Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibGfx/WebP+image: Add support for writing color index transforms, and fix an encoding bug #24483

Merged
merged 17 commits into from
May 31, 2024

Conversation

nico
Copy link
Contributor

@nico nico commented May 29, 2024

If an image has 256 or fewer colors, WebP/Lossless allows storing
the colors in a helper image, and then storing just indexes into that
helper image in the main image's green channel, while setting
r, b, and a of the main image to 0.

Since constant-color channels need to space to store in WebP,
this reduces storage needed to 1/4th (if alpha is used) or 1/3rd
(if alpha is constant across the image).

If an image has <= 16 colors, WebP lossless files pack multiple
color table indexes into a single pixel's green channel, further
reducing file size.

GIFs can store at most 256 colors per frame, so animated gifs
often have 256 or fewer colors, making this effective when
transcoding gifs.

This adds support for writing that color index transform.

It also fixes a general encoding bug that the tests for this transform
exposed.

Some numbers on my test files:

sunset-retro.png: No performance or binary size impact. The input
quickly uses more than 256 colors.

giphy.gif (184k): 4.1M -> 3.9M, 95.5 ms ± 4.9 ms -> 106.4 ms ± 5.3 ms
Most frames use more than 256 colors, but just barely. So fairly
expensive runtime wise, with just a small win.

(See comment on #24454 for the previous 4.9 MiB -> 4.1 MiB drop.)

7z7c.gif (11K): 118K -> 40K
Every frame has less than 256 colors (but more than 16, so no packing),
and so we can cut filesize roughly to 1/3rd: We only need to store an
index per channel. From 10.7x as large as the input to 3.6x as large.

Also add an option to image to choose which transforms are used.
(Even though only one is supported so far, that flag supports all four
already.) animation doesn't have this flag yet; maybe I'll add that later.

nico added 9 commits May 29, 2024 18:24
In an early version of the huffman writing code, we always used 8 bits
here, and the comments still reflected that. Since we're now always
writing only as many bits as we need (in practice, still almost always
8), the comments are misleading.
This way, it can be used to write entropy-coded-images too,
which will be used when writing transforms.
No behavior change, but this makes it easy to correctly set this
flag when adding an indexing transform: Opacity then needs to be
determined based on if colors in the color table have opacity,
not if the indexes into the color table do.

With this struct, only the first time something sets opacity is
honored, giving us those semantics.
If an image has 256 or fewer colors, WebP/Lossless allows storing
the colors in a helper image, and then storing just indexes into that
helper image in the main image's green channel, while setting
r, b, and a of the main image to 0.

Since constant-color channels need to space to store in WebP,
this reduces storage needed to 1/4th (if alpha is used) or 1/3rd
(if alpha is constant across the image).

If an image has <= 16 colors, WebP lossless files pack multiple
color table indexes into a single pixel's green channel, further
reducing file size. This pixel packing is not yet implemented in
this commit.

GIFs can store at most 256 colors per frame, so animated gifs
often have 256 or fewer colors, making this effective when
transcoding gifs.

(WebP also has a "subtract green" transform, which can be used
to need to store just a single channel for grayscale images, without
having to store a color table. That's not yet implemented -- for now,
we'll now store grayscale images using this color indexing transform
instead, which wastes to storage for the color table.)

(If an image has <= 256 colors but all these colors use only a single
channel, then storing a color table for these colors is also wasteful,
at least if the image has > 16 colors too. That's rare in practice,
but maybe we can add code for it later on.)

(WebP also has a "color cache" feature where the last few used colors
can be referenced using very few bits. This is what the webp spec says
is similar to palettes as well. We don't implement color cache writing
support yet either; maybe it's better than using a color indexing
transform for some inputs.)

Some numbers on my test files:

sunset-retro.png: No performance or binary size impact. The input
quickly uses more than 256 colors.

giphy.gif (184k): 4.1M -> 3.9M, 95.5 ms ± 4.9 ms -> 106.4 ms ± 5.3 ms
Most frames use more than 256 colors, but just barely. So fairly
expensive runtime wise, with just a small win.

(See comment on SerenityOS#24454 for the previous 4.9 MiB -> 4.1 MiB drop.)

7z7c.gif (11K): 118K -> 40K
Every frame has less than 256 colors (but more than 16, so no packing),
and so we can cut filesize roughly to 1/3rd: We only need to store an
index per channel. From 10.7x as large as the input to 3.6x as large.
@github-actions github-actions bot added the 👀 pr-needs-review PR needs review from a maintainer or community member label May 29, 2024
nico added 2 commits May 29, 2024 18:37
If an image has <= 16 colors, WebP lossless files pack multiple
color table indexes into a single pixel's green channel, further
reducing file size. This adds support for that.

My current test files all have more than 16 colors. For a 16x16
black-and-white bitmap that contains a little smiley face in the
middle, this reduces the output size from 128B to 54B.
@nico nico force-pushed the webp-color-index branch 2 times, most recently from 72d54f4 to ca5de72 Compare May 29, 2024 22:50
Copy link
Member

@gmta gmta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Userland/Utilities/image.cpp Outdated Show resolved Hide resolved
@nico nico force-pushed the webp-color-index branch 2 times, most recently from a0ea09d to 6e04463 Compare May 31, 2024 19:38
nico added 6 commits May 31, 2024 15:51
Storing a number n needs floor(log2(n) + 1) bits, not ceil(log2(n)).
(The two expressions are identical except for when n is a power of 2.)

Serendipitously covered by the indexed color transform tests in this PR.
Else UBSan complains about the color indexing test in this PR.
This is mainly useful for testing.
@gmta gmta added ✅ pr-maintainer-approved-but-awaiting-ci PR has been approved by a maintainer and can be merged after CI has passed and removed 👀 pr-needs-review PR needs review from a maintainer or community member labels May 31, 2024
@gmta gmta merged commit d810324 into SerenityOS:master May 31, 2024
13 checks passed
@github-actions github-actions bot removed the ✅ pr-maintainer-approved-but-awaiting-ci PR has been approved by a maintainer and can be merged after CI has passed label May 31, 2024
@nico nico deleted the webp-color-index branch May 31, 2024 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants