Commit Graph

4 Commits

Author SHA1 Message Date
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
Nico Weber
b7b51d01c3 LibGfx/AnimationWriter: Survive animations with two identical frames
Creating a completely empty bitmap fails. Just write a 1x1 bitmap
if the bitmap ends up empty for now.
2024-05-22 06:41:47 -04:00
Nico Weber
e2336e2099 LibGfx+animation: Only store changed pixels in animation frames
For example, for 7z7c.gif, we now store one 500x500 frame and then
a 94x78 frame at (196, 208) and a 91x78 frame at (198, 208).

This reduces how much data we have to store.

We currently store all pixels in the rect with changed pixels.
We could in the future store pixels that are equal in that rect
as transparent pixels. When inputs are gif files, this would
guaranteee that new frames only have at most 256 distinct colors
(since GIFs require that), which would help a future color indexing
transform. For now, we don't do that though.

The API I'm adding here is a bit ugly:

* WebPs can only store x/y offsets that are a multiple of 2. This
  currently leaks into the AnimationWriter base class.
  (Since we potentially have to make a webp frame 1 pixel wider
  and higher due to this, it's possible to have a frame that has
  <= 256 colors in a gif input but > 256 colors in the webp,
  if we do the technique above.)

* Every client writing animations has to have logic to track
  previous frames, decide which of the two functions to call, etc.

This also adds an opt-out flag to `animation`, because:

1. Some clients apparently assume the size of the last VP8L
   chunk is the size of the image
   (see https://github.com/discord/lilliput/issues/159).

2. Having incremental frames is good for filesize and for
   playing the animation start-to-end, but it makes it hard
   to extract arbitrary frames (have to extract all frames
   from start to target frame) -- but this is mean tto be a
   delivery codec, not an editing codec. It's also more vulnerable to
   corrupted bytes in the middle of the file -- but transport
   protocols are good these days.
   (It'd also be an idea to write a full frame every N frames.)

For https://giphy.com/gifs/XT9HMdwmpHqqOu1f1a (an 184K gif),
output webp size goes from 21M to 11M.

For 7z7c.gif (an 11K gif), output webp size goes from 2.1M to 775K.

(The webp image data still isn't compressed at all.)
2024-05-14 13:43:03 -04:00
Nico Weber
6c79efcae4 LibGfx: Move AnimationWriter to its own file
No behavior change.
2024-05-14 13:43:03 -04:00