Commit graph

186 commits

Author SHA1 Message Date
Nico Weber ab143e9b0e LibGfx/BMP: Clear alpha in palette entries
The semantics of BGRx8888 aren't super clear and it means different
things for different parts of the codebase. In particular, the PNG
writer still writes the x channel to the alpha channel of its output.

In BMPs, the 4th palette byte is usually 0, which means after #21412 we
started writing all .bmp files with <= 8bpp as completely transparent
to PNGs.

This works around that.

(See also #19464 for previous similar workarounds.)

The added `bitmap.bmp` is a 1bpp file I drew in Photoshop and saved
using its "Save as..." saving path.
2024-03-05 21:27:41 +00:00
Lucas CHOLLET 9ec3480207 LibGfx/TIFF: Add support for Group4Fax encoded images
Note that we don't parse the T6 option group yet.

The test case was generated with GIMP.
2024-02-21 13:49:43 +01:00
Lucas CHOLLET be9ec591e7 LibGfx/CCITT: Add support for Group3 2D
The two test images were created with:
tiffcp ccit3.tiff -c g3:2d ccit3_2d.tiff
tiffcp ccit3.tiff -c g3:2d:fill ccit3_2d_fill.tiff
2024-02-19 01:40:04 +01:00
Tim Ledbetter 3ffa2a39bc Tests: Propagate errors with TRY_OR_FAIL() where possible 2024-02-14 17:46:06 -05:00
Lucas CHOLLET 8e21bbf7bf LibGfx/TIFF: Add support for tiled images
A tile is basically a strip with a user-defined width. With that in
mind, adding support for them is quite straightforward. As a lot the
common code was named after 'strips', to avoid future confusion I
renamed everything that interact with either strips or tiles to a
global term: 'segment'.

Note that tiled images are supposed to always have a 'TileOffsets' tag
instead of 'StripOffset'. However, this doesn't seem to be enforced by
encoders, so we support having either of them indifferently.

The test case was generated with the following Python script:

import pyvips

img = pyvips.Image.new_from_file('deflate.tiff')
img.write_to_file('tiled.tiff',
                  compression=pyvips.ForeignTiffCompression.DEFLATE,
                  tile=True, tile_width=64, tile_height=64)
2024-02-13 10:13:11 +01:00
Lucas CHOLLET b9afac0a06 LibGfx/CCITT: Consider the UseFillBits option 2024-02-12 14:08:56 +01:00
Nico Weber 69964e10f4 LibGfx+Tests: Improve calculation of restart interval
JPEGs can store a `restart_interval`, which controls how many
minimum coded units (MCUs) apart the stream state resets.
This can be used for error correction, decoding parts of a jpeg
in parallel, etc.

We tried to use

    u32 i = vcursor * context.mblock_meta.hpadded_count + hcursor;
    i % (context.dc_restart_interval *
         context.sampling_factors.vertical *
         context.sampling_factors.horizontal) == 0

to check if we hit a multiple of an MCU.

`hcursor` is the horizontal offset into 8x8 blocks, vcursor the
vertical offset, and hpadded_count stores how many 8x8 blocks
we have per row, padded to a multiple of the sampling factor.

This isn't quite right if hcursor isn't divisible by both
the vertical and horizontal sampling factor. Tweak things so
that they work.

Also rename `i` to `number_of_mcus_decoded_so_far` since that
what it is, at least now.

For the test case, I converted an existing image to a ppm:

    Build/lagom/bin/image -o out.ppm \
        Tests/LibGfx/test-inputs/jpg/12-bit.jpg

Then I resized it to 102x77px in Photoshop and saved it again.
Then I turned it into a jpeg like so:

    path/to/cjpeg \
        -outfile Tests/LibGfx/test-inputs/jpg/odd-restart.jpg \
        -sample 2x2,1x1,1x1 -quality 5 -restart 3B out.ppm

The trick here is to:

a) Pick a size that's not divisible by the data size width (8),
   and that when rounded to a block size (13) still isn't divisible
   by the subsample factor -- done by picking a width of 102.
b) Pick a huffman table that doesn't happen to contain the bit
   pattern for a restart marker, so that reading a restart marker
   from the bitstream as data causes a failure (-quality 5 happens
   to do this)
c) Pick a restart interval where we fail to skip it if our calculation
   is off (-restart 3B)

Together with #22987, fixes #22780.
2024-01-30 14:50:43 +01:00
Nico Weber 6971ba35d5 LibGfx+Tests: Support grayscale jpegs with 2x2 sampling and MCU reset
Non-interleaved files always have an MCU of one data unit.

(A "data unit" is an 8x8 tile of pixels, and an "MCU" is a
"minium coded unit", e.g. 2x2 data units for luminance and
1 data unit each for Cr and Cb for a YCrCb image with
4:2:0 subsampling.)

For the test case, I converted an existing image to a ppm:

    Build/lagom/bin/image -o out.ppm \
        Tests/LibGfx/test-inputs/jpg/12-bit.jpg

Then I converted it to grayscale and saved it as a pgm in Photoshop.
Then I turned it into a weird jpeg like so:

    path/to/cjpeg \
        -outfile Tests/LibGfx/test-inputs/jpg/grayscale_mcu.jpg \
        -sample 2x2 -restart 3 out.pgm

Makes 3 of the 5 jpegs failing to decode at #22780 go.
2024-01-30 05:35:22 +01:00
Nico Weber c694d4326b Tests: Add a pam cmyk test file
Hand-written in a text editor.

I verified that `convert` converts it to a png that looks like the
rgb test expectations.
2024-01-26 07:36:53 +01:00
Nico Weber ba8f4e73bf Tests: Add a pam test file
Hand-written in a text editor.

I verified that `convert` converts it to a png that looks like the
test expectations.
2024-01-26 07:36:53 +01:00
Nico Weber 550937f5dd Tests: Add a test cmyk jpeg file with an embedded color profile
I opened Base/res/graphics/buggie.png in Photoshop, converted it
to U.S. Web Coated (SWOP) v2, flattened the image so we don't have
CMYK with alpha, and saved it as a jpeg (with color profile embedded).
2024-01-25 15:53:44 +01:00
Lucas CHOLLET a17041fe7f LibGfx/TIFF: Add support for CMYK
The test case has been generated with Krita.
2024-01-24 22:16:22 -07:00
Lucas CHOLLET 1faf9bb44f LibGfx/TIFF: Apply the HorizontalDifferencing on the alpha channel
When present, the alpha channel is also affected by the horizontal
differencing predictor.

The test case was generated with GIMP with the following steps:
 - Open an RGB image
 - Add a transparency layer
 - Export as TIFF with the LZW compression scheme
2024-01-22 20:10:48 -07:00
Lucas CHOLLET c2c7365494 LibGfx/TIFF: Accept images with a single strip and no RowsPerStrip tag
This tag is required by the specification, but some encoders (at least
Krita) don't write it for images with a single strip.

The test file was generated by opening deflate.tiff in Krita and saving
it with the DEFLATE compression.
2024-01-19 14:13:44 +01:00
Lucas CHOLLET 75d87ccf5f LibGfx/TIFF+CCITT: Start to decode CCITT Group 3 images
We currently only support 1D Group 3, but that's a start.

The test case was generated with GIMP (it happens to be 1D by chance).
2024-01-18 14:00:56 +01:00
Lucas CHOLLET edffdc35a9 LibGfx/TIFF+CCITT: Clarify naming of compression type 2
Type 2 <=> One-dimensional Group3, customized for TIFF
Type 3 <=> Two-dimensional Group3, uses the original 1D internally
Type 4 <=> Two-dimensional Group4

So let's clarify that this is not Group3 1D but the TIFF variant, which
is called `CCITTRLE` in libtiff. So let's stick with this name to avoid
confusion.
2024-01-18 14:00:56 +01:00
Nicolas Ramz 534eeb6c4b LibGfx/ILBMLoader: Properly display images with a bitplane mask
Images with a display mask ("stencil" as it's called in DPaint) add
an extra bitplane which acts as a mask. For now, at least skip it
properly. Later we should render masked pixels as transparent, but
this requires some refactoring.
2024-01-18 13:59:17 +01:00
Lucas CHOLLET 015c47da51 Tests/LibGfx: Use TRY_OR_FAIL more in TestImageDecoder 2024-01-15 23:16:57 -07:00
Nico Weber 3616d14c80 LibGfx/JPEG: Allow decoding more subsampling factors
We now allow all subsampling factors where the subsampling factors
of follow-on components evenly decode the ones of the first component.

In practice, this allows YCCK 2111, CMYK 2112, and CMYK 2111.
2024-01-15 11:20:11 -07:00
Nicolas Ramz a1255cb6c9 LibGfx/ILBMLoader: Don't decode bits once full row has been decoded
We were potentially decoding more bits than needed: this could
trash the next lines if decoder didn't zero the extra bits.
2024-01-14 20:41:25 +01:00
Nico Weber 1b4f9bdbdb Tests: Add a png in Display P3 that shows up as solid color in sRGB
I created a 16-bpp RGB file in Display P3 in photoshop, filled it
with (0, 255, 0), and then drew something on it with (100, 255, 0).

(Since it's a 16-bpp image, 255 ix stored as 0xffff and 100 is stored
as 65535 * 100 / 255 == 0x6464 in the file.)

I verified that Edit->Convert to Profile...->sRGB resulted in an
image filled with (0, 255, 0) in that color space (due to gamut
clipping).

Similar to these:
* https://webkit.org/blog-files/color-gamut/Webkit-logo-P3.png
* https://www.dropbox.com/s/tgarynpj65ouafd/insta-logo.png?dl=1

...but in green instead of in red, and hand-drawn by me so no license
concerns.
2024-01-12 16:20:46 -07:00
Nico Weber 7fb32b6682 LibGfx: Fix off-by-some in Painter::draw_scaled_bitmap_with_transform()
Before this, drawing a 1x1 bitmap scaled up to MxN would only fill
M/2 x N/2 pixel, due to source_point going outside (0, 0).
2024-01-10 09:38:13 +01:00
Nicolas Ramz fc5b6e4dda LiGfx/ILBMLoader: Don't throw if malformed bitplane can be decoded
Some apps seem to generate malformed images that are accepted
by most readers. We now only throw if malformed data would lead to
a write outside the chunky buffer.
2024-01-08 07:21:27 -07:00
Lucas CHOLLET 335097e446 LibGfx/TIFF: Modify the image according to the Orientation tag
Let's use the already existing logic (ExifOrientedBitmap) to modify the
bitmap to honor the orientation tag.
2024-01-08 00:07:44 +01:00
Lucas CHOLLET 402de2985d LibGfx/ICO: Do not try to decode a mask if we already reached EOF
When using the BMP encoding, ICO images are expected to contain a 1-bit
mask for transparency. Regardless an alpha channel is already included
in the image, the mask is always required. As stated here[1], the
mask is used to provide shadow around the image.

Unfortunately, it seems that some encoder do not include that second
transparency mask. So let's read that mask only if some data is still
remaining after decoding the image.

The test case has been generated by truncating the 64 last bytes
(originally dedicated to the mask) from the `serenity.ico` file and
changing the declared size of the image in the ICO header. The size
value is stored at the offset 0x0E in the file and I changed the value
from 0x0468 to 0x0428.

[1]: https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
2024-01-07 12:32:02 -05:00
Lucas CHOLLET 9c54c13744 Tests/LibGfx: Move the ICO test file to the ico directory
And add more tests on the image than just "we are able to decode it".
2024-01-07 12:32:02 -05:00
Lucas CHOLLET 75fc51bb67 Tests/LibGfx: Fix a typo 2024-01-07 10:22:59 +01:00
Andreas Kling 182a2b0c3a LibGfx/GIF: Only parse global color table if header flag is set
This fixes an issue where GIF images without a global color table would
have the first segment incorrectly interpreted as color table data.

Makes many more screenshots appear on https://virtuallyfun.com/ :^)
2024-01-05 13:20:00 +01:00
Lucas CHOLLET b8cbc282f3 LibGfx/TIFF: Don't stop decoding when failing to decode a tag
TIFF files are made in a way that make them easily extendable and over
the years people have made sure to exploit that. In other words, it's
easy to find images with non-standard tags. Instead of returning an
error for that, let's skip them.

Note that we need to make sure to realign the reading head in the file.

The test case was originally a 10x10 checkerboard image with required
tags, and also the `DocumentName` tag. Then, I modified this tag in a
hexadecimal editor and replaced its id with 30 000 (0x3075 as a LE u16)
and the type with the same value as well. This is AFAIK, never used as
a custom TIFF tag, so this should remain an invalid tag id and type.
2024-01-04 14:27:16 +01:00
Lucas CHOLLET 73c8b4865e LibGfx/TIFF: Add AdobeDeflate compression support
This new compression is quite popular and uses a basic Zlib compression
to compress strips. Note that this is not part of the original TIFF
specification but in the Technical Notes from 2002:
https://web.archive.org/web/20160305055905/http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf

The test case was generated with GIMP.
2023-12-29 20:12:07 +01:00
Nico Weber a2bd19fdac LibGfx/JPEG: Make ycck jpegs with just cc subsampled decode correctly
We currently assume that the K (black) channel uses the same sampling
as the Y channel already, so this already works as long as we don't
error out on it.
2023-12-29 09:45:31 -05:00
Nico Weber 4236798244 Tests/LibGfx: Add automated tests for ycck jpegs
Only one of the three test cases pass at the moment.
2023-12-29 09:45:31 -05:00
Nico Weber e735ee5251 Tests/LibGfx: Add YCCK jpeg test files
Obtained by running:

    convert rgb_components.jpg -colorspace cmyk \
        -sampling-factor 1 ycck-1111.jpg
    convert rgb_components.jpg -colorspace cmyk \
        -sampling-factor 2 ycck-2111.jpg
    convert rgb_components.jpg -colorspace cmyk ycck-2112.jpg

where rgb_components.jpg is the file in Tests/LibGfx/test-inputs/jpg.

(I used the web version of `convert` at
https://cancerberosgx.github.io/magic/playground/index.html)

While this does indeed produce a cmyk jpg (using the YCCK encoding
internally), it uses the mathematical rgb->cmyk conversion and does
not embed an cmyk color space in the output jpg.

Normally, cmyk images are for printing and hence converting them
from cmyk to rgb using a color profile like SWOP leads to better
results. So if a cmyk image does not contain color space information,
applications might use something like SWOP instead of the simple
math transform to convert to RGB. Programs doing that will show
these images as fairly muted (and would arguably be correct doing
so).

Hence, tests using these images shouldn't check their RGB values.
Ideally, we'd add a way to get the raw cmyk data from a cmyk jpeg,
and then tests could test color values against that.

The -1111 image uses no subsampling, meaning each channel's sampling
factor is 1.

The -2111 image uses subsampling for the non-Y channels, meaning the
sampling factors are 2 for Y and 1 each for YYK.

The -2112 image uses subsampling for the two C channels, meaning the
sampling factors are 2 for Y and K and 1 each for YY.

We correctly render the -1111 variant (using e.g.
`Build/lagom/bin/image -o out.png .../ycck-1111.jpg).

We render the -2111 variant, but it looks pretty broken.

We refuse to decode the -2112 variant. This is #21259.

Manual tests for now, but having these in tree will make it easier
to write unit tests later, once things work better.
2023-12-29 08:17:10 +00:00
Lucas CHOLLET 67522fab2e LibGfx/TIFF: Add support for RGBPalette images
TIFF images with the PhotometricInterpretation tag set to RGBPalette are
based on indexed colors instead of explicitly describing the color for
each pixel. Let's add support for them.

The test case was generated with GIMP using the Indexed image mode after
adding an alpha layer. Not all decoders are able to open this image, but
GIMP can.
2023-12-23 20:41:48 +01:00
Lucas CHOLLET 2cfca633ca LibGfx/TIFF: Add support for images with UnassociatedAlpha
UnassociatedAlpha is the one used by GIMP when generating TIFF images
with transparency. Support is added for Grayscale and RGB images as it's
the two that we support right now but managing transparency should be
really straightforward for other types as well.
2023-12-22 08:08:47 +00:00
Nicolas Ramz 6ccdb1dc72 LibGfx/ILBMLoader: Add support for 24bit files 2023-12-21 09:19:30 +00:00
Lucas CHOLLET 64912d4d02 LibGfx/TIFF: Add support for images with CCITT3 1D compression
This compression (tag Compression=2) is not very popular on its own, but
a base to implement CCITT3 2D and CCITT4 compressions.

As the format has no real benefits, it is quite hard to find an app that
accepts tho encode that for you. So I used the following program that
calls `libtiff` directly:
```cpp
#include <vector>
#include <cstdlib>
#include <iostream>

#include <tiffio.h>

// An array containing 0 and 1 of length width * height.
extern std::vector<uint8_t> array;
int main() {
    // From: https://stackoverflow.com/a/34257789

    TIFF *image = TIFFOpen("input.tif", "w");
    int const width = 400;
    int const height = 300;
    TIFFSetField(image, TIFFTAG_IMAGEWIDTH, width);
    TIFFSetField(image, TIFFTAG_IMAGELENGTH, height);
    TIFFSetField(image, TIFFTAG_PHOTOMETRIC, 0);
    TIFFSetField(image, TIFFTAG_COMPRESSION, COMPRESSION_CCITTRLE);

    TIFFSetField(image, TIFFTAG_BITSPERSAMPLE, 1);
    TIFFSetField(image, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(image, TIFFTAG_ROWSPERSTRIP, 1);

    std::vector<uint8_t> scan_line(width / 8 + 8, 0);
    int count = 0;
    for (int i = 0; i < height; i++) {
        std::fill(scan_line.begin(), scan_line.end(), 0);
        for (int x = 0; x < width; ++x) {
            uint8_t eight_pixels = scan_line.at(x / 8);
            eight_pixels = eight_pixels << 1;
            eight_pixels |= !array.at(i * width + x);
            scan_line.at(x / 8) = eight_pixels;
        }
        int bytes = int(width / 8.0 + 0.5);
        if (TIFFWriteScanline(image, scan_line.data(), i, bytes) != 1)
            std::cerr << "Something went wrong\n";
    }

    TIFFClose(image);
}
```
2023-12-19 21:01:24 +01:00
Ali Mohammad Pur 5e1499d104 Everywhere: Rename {Deprecated => Byte}String
This commit un-deprecates DeprecatedString, and repurposes it as a byte
string.
As the null state has already been removed, there are no other
particularly hairy blockers in repurposing this type as a byte string
(what it _really_ is).

This commit is auto-generated:
  $ xs=$(ack -l \bDeprecatedString\b\|deprecated_string AK Userland \
    Meta Ports Ladybird Tests Kernel)
  $ perl -pie 's/\bDeprecatedString\b/ByteString/g;
    s/deprecated_string/byte_string/g' $xs
  $ clang-format --style=file -i \
    $(git diff --name-only | grep \.cpp\|\.h)
  $ gn format $(git ls-files '*.gn' '*.gni')
2023-12-17 18:25:10 +03:30
Nicolas Ramz cd6c7f3fc4 LibGfx/ILBMLoader: Add support for PC DeluxePaint files 2023-12-13 10:39:13 +00:00
Lucas CHOLLET 234d084876 LibGfx/TIFF: Add support for bit-depth up to 32 bits per sample
This makes us support every "minisblack" and "rgb-contig" images from
the depth folder of libtiff's test suite:
https://libtiff.gitlab.io/libtiff/images.html
2023-12-09 21:47:33 +01:00
Lucas CHOLLET 25993cd93b Tests: Use TRY_OR_FAIL instead of MUST in TestImageDecoder.cpp
No need to panic the executable on a test failure.
2023-12-09 19:05:45 +01:00
Nicolas Ramz 8d68f94282 LibGfx/ILBMLoader: Add HAM6/HAM8 support 2023-12-07 10:08:29 -07:00
Lucas CHOLLET da134f6867 LibGfx/TIFF: Add support for grayscale images
Images with a single sample per pixel should be interpreted as
grayscale.
2023-12-07 08:17:46 +00:00
Nico Weber b2a1130556 LibGfx/ICC: Implement conversion between different connection spaces
If one profile uses PCSXYZ and the other PCSLAB as connection space,
we now do the necessary XYZ/LAB conversion.

With this and the previous commits, we can now convert from profiles
that use PCSLAB with mAB, such as stress.jpeg from
https://littlecms.com/blog/2020/09/09/browser-check/ :

    % Build/lagom/icc --name sRGB --reencode-to serenity-sRGB.icc
    % Build/lagom/bin/image -o out.png \
        --convert-to-color-profile serenity-sRGB.icc \
        ~/src/jpegfiles/stress.jpeg
2023-12-04 08:02:36 +00:00
Tim Ledbetter aa54007943 LibGfx/TinyVG: Avoid OOM if header contains a bogus color table size
This change limits the amount of memory that is initially allocated for
the color table. This prevents an OOM condition if the file contains an
incorrect color table size.
2023-12-02 10:47:39 +01:00
Lucas CHOLLET 4994609af0 LibGfx/TIFF: Add support for the ICCProfile tag 2023-12-02 10:36:02 +01:00
Lucas CHOLLET 272be6b20a LibGfx/TIFF: Add support for LZW compression 2023-11-12 13:56:27 +01:00
Tim Ledbetter c1d7a51391 LibGfx/ICC: Avoid buffer overrun when creating TextDescriptionTagData
We now validate that the offsets used cannot overflow, preventing
possible buffer overruns.
2023-11-08 09:37:30 +01:00
Tim Ledbetter f56ae8c0e9 LibGfx/ILBM: Ensure CMAP chunk size matches expected value
The color map should be 3 bytes per pixel and should contain
`2^nPlanes` pixels. We now return an error if the color map isn't the
size we expect.
2023-11-08 09:36:01 +01:00
Tim Ledbetter ae6c39e501 LibGfx/ILBM: Ensure decompressed body chunk data is the correct length 2023-11-08 09:36:01 +01:00