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.
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.
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.
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.
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
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.
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.
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.
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.
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);
}
```
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.
Previously, the regression tests for OSS-Fuzz issues 62033 and 63296
used test case files directly from OSS-Fuzz. These files are invalid
in multiple ways because they have been generated by a fuzzer. This
commit replaces these files with ones that only expose the issue being
tested.
An error is now returned if `numTables` is zero or greater than 4096.
While this isn't explicitly mentioned in the specification, subsequent
calculations will be incorrect if the value falls outside this range.
The first implementation of this property was just plain wrong. Looks
like this property isn't used a lot as I found the issue by reviewing
the code and not because of a specific image.
The test image is a 32x32 mosaic of alternating black and yellow pixels,
it was generated using this code:
Bitdepth 8
RCT 1
Width 32
Height 32
if W-WW-NW+NWW > -300
- Set -1000
- Set 900
Currently, the `isobmff` utility will only print the media file type
info from the FileTypeBox (major brand and compatible brands), as well
as the names and sizes of top-level boxes.
This image is exactly the same as the previous one, excepted the RCT
transformation. It has been generated with:
Width 64
Height 64
RCT 29
Upsample 2
Bitdepth 10
if N > 300
- NE -6
- W 6
This image uses the modular encoding with a very simple prediction tree.
It also makes use of two features: upsampling (x2 factor) and a
non-standard bit depth (10 bits). The file has been generated on
https://jxl-art.surma.technology/ , with the following input:
Width 64
Height 64
Upsample 2
Bitdepth 10
if N > 300
- NE -6
- W 6
This tests that we can successfully parse the "everything" TVG files,
which make use of every feature in TinyVG.
Test files taken from https://github.com/TinyVG/examples (MIT).