3078 Commits
Author SHA1 Message Date
Nico Weber bd7be1ae51 LibGfx/JBIG2Writer: Support symbol dicts with more than 32K entries
...as long as fewer than 32K entries are referenced in a single
text region segment.

Having more than 32K entries in a symbol dictionary happens in some
real-world PDFs where the "global" JBIG2 stream that's shared over
all pages has lots of letters. But the text region segment on every
single page just has a couple thousand letters (160 characters per line
at 100 lines and a couple thousand dust speckles is well under 32K
symbol references per page).

I'd like to add a test that's similar to such a file, so we need to
support writing text regions referring to symbol dictionaries with
more than 32K symbols.
2026-09-13 21:12:19 -04:00
Nico Weber f98de99d00 LibGfx: Increase maximum Bitmap dimensions a bit
A test jbig2 image I want to create will have a height class bitmap
that's 9 * 2 ** 18 = 2,359,296 pixels wide (and 2 high).

So bump up our limit on bitmap width and height from 64 ki to 4 Mi.
2026-09-13 21:12:19 -04:00
Lucas Chollet f4bde6ab14 Everywhere: Fix -Wunused-template warnings emited by recent Clang
The latest version of Clang got better at detecting unused templates. It
detected one truly unused template in our codebase. The rest are either
from templated functions that are not always used (depending on the
compile-time configuration) or from templated functions that are
incorrectly annotated with static.
2026-08-22 21:48:49 +02:00
Darren Carreras 007041bb2d LibGfx: Reject BMPs with an unrepresentable height
A signed DIB height may be negative for top-down images, but the
minimum signed value cannot be negated. Reject it while parsing so
size and frame decoding cannot overflow.
2026-08-22 09:41:36 -04:00
Nico Weber 389c8d4911 LibGfx: Remove now-unused RepeatingBitmapPaintStyle, OffsetPaintStyle 2026-08-16 14:01:15 -04:00
Nico Weber 44d5e571df Everywhere: Use Rect::scaled_to_fit_within() and Rect::scaled_to_cover()
This is a slight behavior change in some places. Some places did

    (r.size().to_type<float>() * max(scale_x, scale_y)).to_type<int>();

After this commit, the semantics are now like `(...).to_rounded<int>()`,
that is we no longer sometimes omit a column or row in the fitted
direction.
2026-07-26 10:13:47 -04:00
Nico Weber 35d63ed139 LibGfx: Add Rect::scaled_to_fit_within() and Rect::scaled_to_cover()
scaled_to_fit_within() is like CSS's `object-fit: contain`: It returns
the largest rect that completely fits into the given rect, with an exact
fit in one dimension, and centered in the other dimension.

scaled_to_cover() is like `object-fit: cover`: It returns the smallest
rect that completely covers the given rect, with an exact fit in one
dimension, and centered in the other dimension.
2026-07-26 10:13:47 -04:00
Nico Weber dc4f01f580 LibGfx/JPEG2000/IDWT: Combine steps 1 and 2 for 9-7 _1D_FILTR
Takes `Build/lagom/bin/image -o out.tiff ~/Downloads/balloon.jpf` from
531.4 ms ± 5.4 ms to 515.8 ms ± 4.7 ms ms under hyperfine on my system
(2.7% less). When run under `/usr/bin/time -l`, the changes are:

Before:

         13202665108  instructions retired
          2281080551  cycles elapsed

After:

         12821908788  instructions retired
          2213264986  cycles elapsed

Only affects lossy files.
2026-07-26 10:12:58 -04:00
Nico Weber fd743c7011 LibGfx/JPEG2000/IDWT: Name loop boundaries in _1D_FILTR
No performance or behavior change.
2026-07-26 10:12:58 -04:00
Nico Weber 32a71fa3aa LibGfx/JPEG2000/IDWT: Use raw pointers instead of op[] in _1D_EXTR
Takes `Build/lagom/bin/image -o out.tiff ~/Downloads/balloon.jpf` from
542.3 ms ± 7.0 ms to 527.2 ms ± 4.7 ms under hyperfine on my system
(2.7% less). When run under `/usr/bin/time -l`, the changes are:

Before:

         13956342230  instructions retired
          2322276362  cycles elapsed

After:

         13251294364  instructions retired
          2270195534  cycles elapsed

The absolute win on the lossless file is again a bit smaller
(due to smaller image dimensions), and the relative win is tiny
there (due to it spending much more time in bitplane decoding and
less in IDWT).
2026-07-26 10:12:58 -04:00
Nico Weber 83a0675316 LibGfx/JPEG2000/IDWT: Call PSE only when needed in _1D_EXTR
Takes `Build/lagom/bin/image -o out.tiff ~/Downloads/balloon.jpf` from
586.0 ms ± 5.6 ms to 547.7 ms ± 5.1 ms under hyperfine on my system
(6.6% less). When run under `/usr/bin/time -l`, the changes are:

Before:

         14557698316  instructions retired
          2500654210  cycles elapsed

After:

         13958498350  instructions retired
          2318667968  cycles elapsed

The lossless files goes from 1.025 s to 1.000 s (likely smaller win
because it's half the dimensions).

(Note that the "from" hyperfine number is pretty different from the
previous commit, probably due to my system being under different load,
at different battery level, or what. But the "instructions retired"
are very comparable, so that's probably a better number to look at.)
2026-07-26 10:12:58 -04:00
Nico Weber 23925266f3 LibGfx/JPEG2000/IDWT: Use raw pointers instead of op[] in _1D_FILTR
Slight subtlety: Forming the pointer
`data() + (buffers.scanline_start - i0)` is usually technically invalid
in C++: scanline_start is usually 4 and i0 can be up to the current
rect's left or top, so it'll usually point to before the image.

I haven't seen this cause actual problems in practice, but make the
buffers max(left,top) longe, even if that leading data ine is never
touched.

Takes `Build/lagom/bin/image -o out.tiff ~/Downloads/balloon.jpf` from
704.3 ms ± 5.4 ms to 613.2 ms ± 3.9 ms under hyperfine on my system
(12.9% less). When run under `/usr/bin/time -l`, the changes are:

Before:

         18035577126  instructions retired
          2862272914  cycles elapsed

After:

         14521350138  instructions retired
          2492032301  cycles elapsed

balloon.jps uses the lossy 9-7 transform. For lossless files, this helps
too, but less, as the 5-3 transform uses fewer loops in this function.
Also, lossless files spend relatively much more time in bitplane
decoding and less in IDWT, so the relative win for them is smaller too:
For ballon_rev_half.jp2, which is balloon.jpf resized to 50% and
reencoded in lossless, from 1.058 s to 1.035 s.
2026-07-26 10:12:58 -04:00
Nico Weber c6bf4ceda2 LibGfx/JPEG2000: Copy JPEG2000InverseDiscreteWaveletTransformReference.h
I'd like to make changes to our JPEG2000 IDWT implementation to speed
it up. At the moment, it's very close to the spec, which seems useful
for correctness and understandability.

So copy the existing file, put the code in there in a Reference
namespace, and in JPEG2000_DEBUG builds, compare the output of the
two implementations. (At the moment, both implementations are
identical, but that will change more and more over time.)

That way, we can run `Meta/test_pdf.py` with a large test corpus with
the comparison turned on to make sure IDWT optimizations aren't
completely broken.

Run `git show` with -C / --find-copies on this commit to make it display
the new file as copy of the old one, along with changes I made to it.
(Namely: I removed the input structs from the reference file as they're
shared with the production file, and I qualified a few calls with
`Reference::`.)

No behavior change.
2026-07-26 10:12:58 -04:00
Nico Weber f261084476 LibGfx/JPEG2000: Pull dequantization step size computation out of loop
step_size is identical for all coefficients. No behavior change.

Takes

    hyperfine \
        'Build/lagom/bin/image --no-output ~/Downloads/balloon.jpf'

from 741.7 ms ± 8.0 ms to 648.0 ms ± 1.9 ms on my system,
a 12.5% reduction.
2026-07-22 08:53:37 -04:00
MacDue e103b1ec36 LibPDF: Separate resolving glyph IDs from drawing glyphs
This will make reusing the logic to determine glyph IDs easier.

No behaviour change intended.
2026-07-20 22:17:51 -04:00
Andrew Kaster bda3dac389 LibGfx: Add helper to load Bitmaps from a Core::Resource URI
This is a prerequisite to allow running GUI apps on non-serenity systems
where `/res` is not a valid path. It defers to the loaded resource
implementation, which by default loads from file.
2026-07-12 14:42:21 -04:00
Nico Weber e897b9d744 Everywhere: Use more granular AK/Math/ includes in a few headers
...and in a few cpp files too, and clean up missing includes.

Reduces number of files recompiled after touching AK/Math/Hyperbolic.h
from a couple thousand to a handful.

No behavior change.
2026-06-21 15:26:06 -04:00
Timothy Flynn aed345cae9 LibGfx: Select largest ICO image before bpp tie-break
When choosing the best image from an ICO file, prefer the entry with the
largest pixel area first, and only use bits-per-pixel as a tie-breaker.
This had regressed in commit b10fe7c136.

This fixes ICO files like Discord's favicon, where 16x16, 32x32, 48x48,
and 256x256 entries all advertise the same bpp. We were previously just
choosing whichever we saw first, which happened to be the 16x16 icon.

(cherry picked from commit b7c4cd511f0f1b02b90dc7be66333617e5a84cb3)
2026-05-28 14:15:17 +02:00
Nico Weber ee896e107e LibGfx/WebP: Remove an unused variable
Found by gcc 16's Wunused-but-set-variable.

No behavior change.
2026-05-03 21:43:21 +02:00
Nico Weber d2a717171b WebP/Lossy: Fix benign typo in a variable name
No behavior change.
2026-05-02 19:48:11 -04:00
Nico Weber b7ef497417 LibGfx+LibMedia: Let decode_webp_chunk_VP8_contents handle non-keyframes
VP8Header now has an optional field with the keyframe data.

Move rejecting of non-keyframes out of decode_webp_chunk_VP8_contents()
into WebPLoader.cpp, decode_webp_first_chunk().

In VP8/Decoder.cpp, we can now check if that optional has a value to
detect if a frame is a keyframe, instead of having to rely on
decode_webp_chunk_VP8_contents() returning an error.

No real behavior change.
2026-05-02 19:48:11 -04:00
Nico Weber b61e1d4967 WebP/Lossy: Remove size_of_first_partition from VP8Header
d1b5eec154 added first_partition / second_partition fields,
so it's no longer necessary to store size_of_first_partition here.
It's identical to first_partition.size(), and nothing reads it.

No behavior change.
2026-05-02 19:48:11 -04:00
Lucas Chollet f1179bf895 LibGfx: Remove unused static variable in SystemTheme
This was found by Clang 23's -Wunused-but-set-global.
2026-05-02 20:04:32 +02:00
Lucas CHOLLET 97b817817a LibGfx/JPEG: Add a deringing pass to the encoder
The idea is described here https://kornel.ski/deringing/. And allows
reducing the noise around sharp white edges. This is visible when
encoding an image like `buggie.png`. One nice aspect of the optimization
is that it only affects macroblocks where it can help.
2026-02-27 17:42:36 +01:00
Sönke Holz e6f97c316d AK+Userland: Add and use the UnsignedIntegral concept
Unlike the previous commit, IsUnsigned is never true for floating-point
types, since there are no unsigned floats.
However, it's still nice to keep the intent clear that this concept
is only for integral types.
2026-02-08 15:51:33 +01:00
Nico Weber df40f5b88e LibGfx/ICC: Allow two more "primary platform" entries
argyll's `targen` writes '*nix' to this value. This is not allowed
per spec as far as I understand (see included link), but argyll-
created profiles are encountered in the wild, for example here:
https://www.thregr.org/wavexx/rnd/20260201-remarkable_pro_colors/#downloadable-files

So allow it. With this, `icc` can dump the profile at that URL.

The v2 spec allows 'TGNT', so allow that too while here.
2026-02-05 06:18:17 -05:00
Nico Weber a3fda9ad04 LibGfx/TIFFWriter: Make writing tiff files much faster
Before this change:

    % hyperfine 'Build/lagom/bin/image --no-output tmp.tiff'
    Benchmark 1: Build/lagom/bin/image --no-output tmp.tiff
      Time (mean ± σ):     245.3 ms ±   1.6 ms
                           [User: 236.7 ms, System: 7.4 ms]
      Range (min … max):   243.8 ms … 248.6 ms    12 runs

    % hyperfine 'Build/lagom/bin/image -o tmp2.tiff tmp.tiff'
    Benchmark 1: Build/lagom/bin/image -o tmp2.tiff tmp.tiff
      Time (mean ± σ):     544.8 ms ±   7.6 ms
                           [User: 525.9 ms, System: 16.0 ms]
      Range (min … max):   539.4 ms … 565.7 ms    10 runs

After this change (--no-output time does not change):

    % hyperfine 'Build/lagom/bin/image -o tmp2.tiff tmp.tiff'
    Benchmark 1: Build/lagom/bin/image -o tmp2.tiff tmp.tiff
      Time (mean ± σ):     266.6 ms ±   1.6 ms
                           [User: 251.9 ms, System: 13.5 ms]
      Range (min … max):   264.8 ms … 269.3 ms    11 runs

tmp.tiff is a 4096x4096 RGB TIFF file.

So the time spent writing a 4096x4096 RGB TIFF file goes from
~300ms to ~20ms, over 10x as fast.

image.cpp does use a OutputBufferedFile, but calling
stream.write_value<u8>() almost 50 million times (4096x4096x3)
at 5.8 ns does explain that overhead. And OutputBufferedFile
does need a virtual call, bounds check at each call, and
TRY() handling at each call, so it seems believable that its
enough to create that much overhead.

(Making a buffer the size of the whole data and calling
write_until_depleted() just once at the end even gets to 250 ms,
but this here seems like a nice time/memory tradeoff.)

No behavior change.
2026-02-02 12:22:52 +01:00
Nico Weber 92d36c0bb0 LibGfx/ICC: Let lerp_nd() take sample as template arg, not Function
Reduces the time for

    Build/lagom/bin/image --no-output \
        --convert-to-color-profile sRGB out-cmyk.tiff

from 3.2s to 2.5s on my machine, a 22% speedup (!).

out-cmyk.tiff is one of the CMYK outputs described in
https://github.com/SerenityOS/serenity/pull/26459#issuecomment-3619058241

Before, the lambdas got passed as an AK::Fuction, and the optimizer
apparently isn't able to undo that after inlining, requiring a virtual
dispatch per call.
2026-02-01 09:19:18 -05:00
Nico Weber 07ec611ad1 LibGfx/JBIG2Writer+Tests: Write code lengths for all referenced symbols
The writer wrote code lengths for all used symbol IDs, but the
spec wants us to write a code length for all referenced symbols.

The loader already got this right, but the writer didn't. (I noticed
this while creating the previous test case and locally futzed around
with symbol IDs.)

While here, also check that all symbol IDs in a text region are
in bounds.
2026-01-27 20:09:06 -05:00
Nico Weber c2baca4135 LibGfx/JBIG2Writer: Fix a comment grammar-o 2026-01-27 20:09:06 -05:00
Nico Weber d861e29ce7 LibGfx+Tests: Support refining strip-refined symbols with export control
When we don't export some symbols, symbol indices are different in
different symbol dictionary sections.

For example, in the included test, segment 1 exports 7 symbols, and
segment 2 refers to it, but only exports 3 of these 7 symbols, and it
only exports 1 of the 4 symbols itself defines. So within segment 2,
indices 0-10 are valid.  But in segment 3, which refers to segment 2,
only indices 0-3 are valid, and they refer to the symbols that have
indices 0, 1, 6, and 10 in segment 2.

This is mostly a problem for the writer (see #26510 / d15f57455a).

When the text region refines its symbol 3, the code used to look up the
symbol ids that it referred to. But those indices were in the namespace
of segment 2, but we used the symbol list from segment 3.

As a fix, don't store symbol dictionary results as a
SymbolDictionarySegmentData::HeightClass::Symbol but just as a
BilevelImage.  For regular symbols and regular refined symbols, it
doesn't make a difference.  Strip-refined symbols are converted to
bitmaps eagerly instead of only when they are refined, but in return
this needs less code, it handles indices correctly, and chains of
strip-refined symbols are no longer O(n^2) (...not that these happen in
practice).

The new test is the same as bitmap-symbol-symbolrefine-textrefine.json
(or bitmap-symbol-symhuffrefine-textrefine.json), but the last
symbol_region sets `export_flags_for_referred_to_symbols` to not export
temporary symbols from its referred-to symbol dictionaries, and it sets
`"exported"` to false for its internal symbols (and symbol_ids are
adjusted to match).
2026-01-27 09:09:51 -05:00
Nico Weber a51fd6b689 LibGfx: Support reading JXL data from jxlp boxes
Per format_overview.md:

> The codestream can optionally be split into multiple jxlp boxes;
> conceptually, this is equivalent to a single jxlc box that contains
> the concatenation of all partial codestream boxes.

It looks like the data is preceded by a u32 that's incrementing,
and has its highest bit set at the end.

Naively implement this by just appending the data from all jxlp
into a single vector.

With this, decoding the output of

    cjxl Tests/LibGfx/test-inputs/jpg/big_image.jpg big_image.jxl

gets a bit farther.
2026-01-08 10:14:33 +01:00
Nico Weber f578d12c91 LibGfx/ISOBMFF: Let isobmff dump jxl signature
With this, `isobmff` can dump the signature contents of a `JXL `
signature box.

JPEGXLImageDecoderPlugin::sniff() already checks for the right contents
of `signature`, so it's fine to remove the FIXME.
2026-01-08 10:14:33 +01:00
Bastiaan van der Plaat 095c28e71a WindowServer+LibGfx: Add overlay background and text color theme options 2026-01-07 23:17:45 +01:00
Sönke Holz 44a54aa4ac LibGfx/JPEGXL: Remove an OSS-Fuzz workaround
Building OSS-Fuzz locally showed that this workaround doesn't seem to
be necessary anymore.
2026-01-04 13:47:22 +01:00
Lucas CHOLLET e5bc1dff23 LibGfx: Make Bayer dithering more luminosity-conservative
On average, this patch reduces the error in luminosity between an input
image and its dithered equivalent. This is done by correcting an off-by-
one error in the threshold computation and also by making sure the range
of mapped gray values to matrix pattern is of equal size for all ranges.
This means that all values between 0 and 255 / (N * N + 1) (51 for
Bayer2x2) will result in a black pixel while only a value of 0 would
previously do it.

The test works by first generating gray bitmap, then applying Bayer
dithering on them and finally compare the luminosity of the two.
2026-01-01 11:05:00 -05:00
Nico Weber d15f57455a LibGfx/JBIG2Writer+Tests/LibGfx: Refinement of symbol strip refines
This adds writer support for refinement of a symbol dictionary entry
that is itself a strip refinement.

This case is a bit awkward for the writer: To write the refinement, we
need the bitmap that's being refined. But since that's a strip refine,
we don't have that bitmap, we only have the parts it consists of.

As #26411 explains, the approach of calling into the loader to do
the strip decoding doesn't work here: We need the decoded strip
refinement while writing the strip itself. So we'd have to send
a partially encoded strip to the loader, which isn't something we
can currently do.

But since strip refinements only use a limited subset of full text
strips (always a background color of 0, always a reference corner
of topleft, always a composition operator of "or"), reimplementing
the loader's logic for this case, while conceptually not nice, is
not a lot of code. So let's just do that.

Using this, also add a test for this feature. I believe this tests
the last missing refinement case :^)

The test uses regular symbols for the eyes (symbols 0 and 1) and the
mouth (symbol 6).

The nose, 100x100 pixels, is conceptually split into 60x60, 40x60,
60x40, 40x40 tiles. Since symbols are sorted by increasing height, the
latter two are symbols 2 and 3, the former are symbols 4 and 5.

Symbol 3 isn't actually the bottom right part of the nose, but
contains the pixel data of an eye. This is for testing refinement
below.

To test strip background color filling, the 60x60 tile is actually
59x60 pixels. And to test refinement, the top left tile is actually
solid white instead of the top left part of the nose.

The first refinement symbol (id 7) refines the top left tile to a 59x60
tile that's the top left corner of the nose, but shifted several pixels
to the right.

The second refinement symbol (id 8) is a strip refinement. It puts
symbols 7 and 5 next to each other, to produce the top half of the nose
(with the left part being shifted to the side).

The third refinement symbol (id 9) is a simple refinement of symbol 8
refines it to the actual top of the nose (to fix the shift, and to
test refinement of strips in a symbol dictionary).

The fourth refinement symbol (id 10) is a strip refinement that puts
symbol 9 in the first strip, with an offset of 1, and symbols 2 and 3
in a second strip. This means symbol 10 is almost the nose, but its
bottom right contains the pixel data of an eye, since that's what
symbol 3 contained.

Finally, the text region refines symbol 10 to the actual nose pixels,
to test refinement of strips from a text region.

Whew!

Since the text region refines to the nose, it's useful to locally remove
that refinement to test that the reference bitmap looks as expected.
Similarly, it's useful to locally make the final strip refinement put
symbol 8 instead of symbol 9 in the first strip, to make sure the input
to symbol 9 looks as expected.

The test file decodes fine in all viewers I've tried (PDFium,
Preview.app, pdf.js, mutool). (This case is much more awkward for the
writer than for the loader, after all.)
2025-12-29 07:28:27 -05:00
Nico Weber 0add8d93a5 LibGfx/JBIG2Loader: Make a JBIG2 quirk slightly less permissive
Since it's easy to do now, only allow references to dead pattern
dictionary segments in Power JBIG2 files, not to all segments.
That's enough to still be able to decode all Power JBIG2 files.
(Except for files 042_13.jb2, 042_14.jb2, which didn't decode before
either, and which don't decode in any viewer that I tried.)
2025-12-27 07:53:16 -05:00
Nico Weber f25dec02fa LibGfx/JBIG2Loader: In permissive mode, allow refs to dead symbol dicts
jbig2enc, which is used by Google Books, sometimes writes files that
are technically invalid. Allow that in permissive mode.

To be able to check the referred-to segment's types, switch the loop
from iterating over header.referred_to_segment_numbers to iterating
over referred_to_segments directly (possible after the third commit
of #26191, 72cd151fb9).

Fixes #26405.
2025-12-27 07:53:16 -05:00
Nico Weber 8fd767b129 LibGfx/JBIG2Loader+jbig2-from-json: Make JBIG2Loader strictness settable
Clients of JBIG2Loader can now pick how strictly invalid images will
be rejected.

By default, it's set to permissive, which allows spec violations that
happen in practice and are easy to work around. At the moment, this
is just for images in the Power JBIG2 suite, but this will be used to
relax some other checks soon.

jbig2-from-json now requests strict spec-compliant checking. This for
now has the effect that manually adding a comment advertising a file
as being a Power JBIG2 file will no longer allow a few quirks when
using jbig2-from-json.
2025-12-27 07:53:16 -05:00
Nico Weber 00cacab329 LibGfx/JBIG2Loader: Rename Power JBIG2 quirks mode variable
A future commit will optionally make JBIG2Loader more strict,
and then this will be false even for Power JBIG2 files. So
`allow_power_jbig2_quirks` seems like a better name.
2025-12-27 07:53:16 -05:00
Nico Weber a9ae56f9a3 LibGfx/JBIG2: Handle text regions, symbol dictionaries without symbols
As far as I can tell, nothing in the spec forbids symbol dictionaries to
have 0 symbols, or text regions to not use any symbols.

Section 7.3.1 even explicitly says that text regions 'may refer to any
number of segments of type "symbol dictionary"', which sounds like it
includes 0.

The spec does say that ceil(log2(num_symbols)) should be computed
though, which is undefined if num_symbols is 0. But ceil(log2(1)) is
0, and using 0 for ceil(log2(0)) feels like what the spec intends.
Furthermore, this seems to be the behavior of other jbig2 decoders.

ceil_log2(x) is 0 for x == 0, so just switching from ceil(log(x))
to AK::ceil_log(x) has the effect that we want.

The test has one text region referring to a symbol dictionary without
symbols, and a text region not referring to any symbol dictionaries,
to make sure that those are drawn without exceptions.
Finally, it has a generic region with the actual image data on top.
2025-12-24 09:19:17 -05:00
Nico Weber 6bb237f47e LibGfx/JBIG2: Use ceil_log2 more
ceil_log2(x) is like ceil(log2(x)) for integer types. One difference
is that ceil_log2(x) is 0 for x == 0, while ceil(log(x)) is not defined
for x == 0.

In these cases, x is guaranteed to be > 0, so both should be equivalent
here.

No behavior change.
2025-12-24 09:19:17 -05:00
Nico Weber 99fb2b4a96 LibGfx/JBIG2Writer+jbig2-from-json: Allow non-identity halftone grids
A halftone region's graymap stores an index that picks a bitmap
from the pattern dictionary. For each graymap pixel, an affine transform
maps that pixel to a position in the output image, and the pattern is
drawn at that position.

So far, we only supported an identity grid that neatly tiled the pattern
bitmaps across the image.

Now we support the full affine transform. This allows for a rotated
grid (for the top left positions; the pattern bitmaps are always drawn
axis-aligned from the top left position).

For tile generation for tests, we just collect tiles off that grid,
and then match against those in graymap generation. This means the
test json file needs to contain numbers that make sense: We currently
don't automatically compute a good rotated grid that covers the
output image.

Tile generation for pattern images clipped on the left or right is
probably wrong. Since the test image has a generous white border,
it doesn't negatively affect our tests, but it's something we might
want to improve in the future.

For `pattern_dictionary`, if the grid parameters are omitted, they
default to an identity grid. Else, it's up to the author of the
json file to make sure the parameters match what's used in the
corresponding halftone region.

For rotated grids, the pattern bitmaps need to overlap to not
have gaps. The pattern construction method currently assumes
that `combination_operator` of `or` is used for the halftone
region.
2025-12-21 19:34:50 -05:00
Nico Weber a80ae20a0f LibGfx/JBIG2Writer: Add a spec comment 2025-12-21 19:34:50 -05:00
Nico Weber 5cd22bb99c LibGfx/JBIG2Writer: Remove an incorrect fixme 2025-12-18 18:18:46 -05:00
Nico Weber 9b9df586d6 LibGfx/JBIG2Writer: Add support for writing halftone skip patterns
Skip patterns are really only interesting for halftone regions with
a rotated grid, which we can't do yet. So the skip pattern will be
all "don't skip" entries everywhere for now.

But once we do add support for non-identity grids, this will hopefully
just work. And even with an identity grid, this already produces a
useful test file.
2025-12-18 18:18:46 -05:00
Nico Weber bb237c3342 LibGfx/JBIG2: Move halftone skip pattern construction to JBIG2Shared
No behavior change.
2025-12-18 18:18:46 -05:00
Nico Weber 4f080c4a88 LibGfx/ICC: Implement converting from PCS to LutBToATagData with clut
Similar to what #26452 did for Lut8TagData and #26462 for Lut16TagData.

With this, we can convert from PCS to all tag types :^)

Which means our ICC implementation is now fairly complete! We don't
support DeviceLink or Abstract profile types (both don't appear in
images; both are arguably useful in some not-embedded-in-images
scenarios), nor the optiona BToD / DToB tags, and are missing
some other minor things (like grayscale profiles -- but not much
is missing here). But overall, this is now a fairly complete (if slow)
basic ICC implementation :^)
2025-12-11 07:10:39 -05:00
Nico Weber 390a7349f1 LibGfx/ICC+icc: Add built-in profiles using mAB / mBA tags
These are really only useful for testing, so don't mention them
in `icc --help` output for `-n`.

They're supposed to behave identically to the built-in XYZ and
LAB profiles (but not the LAB_mft2 profile, which uses a slightly
different internal encoding, see #26462. The new XYZ profiles added
here behave like the XYZ profile described there, but the new LAB
profiles do not behave like the LAB_mft2 profile described there,
but like the LAB (no _mft2) profile:

    % echo 'pcslab(100, 127, -128)' | \
          icc -n LAB_mABmBA_u8_clut --stdin-u8-from-pcs | \
          icc -n LAB --stdin-u8-to-pcs
    pcslab(100, 127, -128)

    % echo 'pcslab(100, 127, -128)' | \
          icc -n LAB_mABmBA_u16_clut --stdin-u8-from-pcs | \
          icc -n LAB --stdin-u8-to-pcs
    pcslab(100, 127, -128)

    % echo 'pcslab(100, 127, -128)' | \
          icc -n LAB_mABmBA_no_clut --stdin-u8-from-pcs | \
          icc -n LAB --stdin-u8-to-pcs
    pcslab(100, 127, -128)

The profiles without a CLUT already round-trip fine, so add
tests for those.
2025-12-11 07:10:39 -05:00