Files
serenity/Userland/Libraries/LibGfx/ImageFormats/AnimationWriter.h
Nico Weber d0b1598806 LibGfx/AnimationWriter: Compress identical pixels in consecutive frames
AnimationWriter already only stores the smallest rect that contains
changing pixels between two frames. For example, when doing a screen
recording and only the mouse cursor moves, we already only encode
the pixels in the (single) rectangle containing old and new mouse cursor
positions.

Within that rectangle, there can still be many pixels that are identical
over the two frames. When possible, we now replace all identical pixels
with transparent black. This has two advantages:

1. It can reduce the number of colors in the image. In particular,
   for wow.gif (and likely many other gifs), new frames had more
   than 256 colors before, and have fewer than 256 colors after this
   change.

2. Long run of identical pixels compress better.

In some cases, this transform might make things slighly worse,
for example if the input image already consists of long runs of
a single color. We'll now add another color to it (transparent black),
without it helping much. And the decoder now must do some blending,
slowing down decoding a bit.

But most of the time this should be a pretty big win. We can tweak
the heuristic when to do it later.

This transform is possible when:

* The new frame doesn't already have transparent pixels (which are
  different from the old frame)

* The encoder/decoder can handle frames with transparent pixels

For the latter reason, encoders currently have to opt in to this.
2024-07-04 10:04:54 +02:00

50 lines
1.7 KiB
C++

/*
* Copyright (c) 2024, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Point.h>
namespace Gfx {
class AnimationWriter {
public:
virtual ~AnimationWriter();
enum class BlendMode {
// The new frame replaces the data below it.
Replace,
// The new frame is blended on top of the data below it.
// Use only when the new frame has completely opaque and completely transparent pixels.
// The opaque pixels will replace the pixels below them, the transparent pixels will leave pixels below them unchanged.
// Use only with AnimationWriter subclasses that return true from can_blend_frames().
Blend,
};
// Flushes the frame to disk.
// IntRect { at, at + bitmap.size() } must fit in the dimensions
// passed to `start_writing_animation()`.
virtual ErrorOr<void> add_frame(Bitmap&, int duration_ms, IntPoint at = {}, BlendMode disposal_method = BlendMode::Replace) = 0;
// If this is set to Yes and can_blend_frames() returns true, add_frame_relative_to_last_frame() may
// call add_frame() with BlendMode::Blend and a frame that has transparent pixels.
enum class AllowInterFrameCompression {
No,
Yes,
};
ErrorOr<void> add_frame_relative_to_last_frame(Bitmap&, int duration_ms, RefPtr<Bitmap> last_frame, AllowInterFrameCompression = AllowInterFrameCompression::Yes);
virtual bool can_blend_frames() const { return false; }
private:
bool can_zero_out_unchanging_pixels(Bitmap& new_frame, Gfx::IntRect new_frame_rect, Bitmap& last_frame, AllowInterFrameCompression) const;
};
}