Skip to content

Commit 39697f4

Browse files
authored
Merge pull request #1646 from SixLabors/bp/tiff4bitaligned
Make sure encoding 4bit paletted tiff rows are byte aligned
2 parents f2deb47 + d5c5d67 commit 39697f4

File tree

7 files changed

+57
-15
lines changed

7 files changed

+57
-15
lines changed

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1)
202202

203203
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
204204
{
205-
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
205+
DebugGuard.IsTrue(plane == -1, "Expected Chunky planar.");
206206
bitsPerPixel = this.BitsPerPixel;
207207
}
208208
else

src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,38 @@ public TiffPaletteWriter(
5555
/// <inheritdoc />
5656
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
5757
{
58-
Span<byte> pixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height);
58+
Span<byte> indexedPixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height);
5959
if (this.BitsPerPixel == 4)
6060
{
61-
using IMemoryOwner<byte> rows4bitBuffer = this.MemoryAllocator.Allocate<byte>(pixels.Length / 2);
61+
int width = this.Image.Width;
62+
int halfWidth = width >> 1;
63+
int excess = (width & 1) * height; // (width % 2) * height
64+
int rows4BitBufferLength = (halfWidth * height) + excess;
65+
using IMemoryOwner<byte> rows4bitBuffer = this.MemoryAllocator.Allocate<byte>(rows4BitBufferLength);
6266
Span<byte> rows4bit = rows4bitBuffer.GetSpan();
63-
int idx = 0;
64-
for (int i = 0; i < rows4bit.Length; i++)
67+
int idxPixels = 0;
68+
int idx4bitRows = 0;
69+
for (int row = 0; row < height; row++)
6570
{
66-
rows4bit[i] = (byte)((pixels[idx] << 4) | (pixels[idx + 1] & 0xF));
67-
idx += 2;
71+
for (int x = 0; x < halfWidth; x++)
72+
{
73+
rows4bit[idx4bitRows] = (byte)((indexedPixels[idxPixels] << 4) | (indexedPixels[idxPixels + 1] & 0xF));
74+
idxPixels += 2;
75+
idx4bitRows++;
76+
}
77+
78+
// Make sure rows are byte-aligned.
79+
if (width % 2 != 0)
80+
{
81+
rows4bit[idx4bitRows++] = (byte)(indexedPixels[idxPixels++] << 4);
82+
}
6883
}
6984

70-
compressor.CompressStrip(rows4bit, height);
85+
compressor.CompressStrip(rows4bit.Slice(0, idx4bitRows), height);
7186
}
7287
else
7388
{
74-
compressor.CompressStrip(pixels, height);
89+
compressor.CompressStrip(indexedPixels, height);
7590
}
7691
}
7792

@@ -91,7 +106,7 @@ private void AddColorMapTag()
91106
PixelOperations<TPixel>.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48);
92107

93108
// It can happen that the quantized colors are less than the expected maximum per channel.
94-
var diffToMaxColors = this.maxColors - quantizedColors.Length;
109+
int diffToMaxColors = this.maxColors - quantizedColors.Length;
95110

96111
// In a TIFF ColorMap, all the Red values come first, followed by the Green values,
97112
// then the Blue values. Convert the quantized palette to this format.

tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// ReSharper disable InconsistentNaming
55
using System;
66
using System.IO;
7-
7+
using SixLabors.ImageSharp.Formats;
88
using SixLabors.ImageSharp.Formats.Tiff;
99
using SixLabors.ImageSharp.Metadata;
1010
using SixLabors.ImageSharp.PixelFormats;
@@ -37,6 +37,7 @@ public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
3737
[InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
3838
[InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
3939
[InlineData(Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)]
40+
[InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)]
4041
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
4142
{
4243
var testFile = TestFile.Create(imagePath);
@@ -91,6 +92,19 @@ public void TiffDecoder_CanDecode_Uncompressed<TPixel>(TestImageProvider<TPixel>
9192
public void TiffDecoder_CanDecode_WithPalette<TPixel>(TestImageProvider<TPixel> provider)
9293
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
9394

95+
[Theory]
96+
[WithFile(Rgb4BitPalette, PixelTypes.Rgba32)]
97+
[WithFile(Flower4BitPalette, PixelTypes.Rgba32)]
98+
[WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)]
99+
public void TiffDecoder_CanDecode_4Bit_WithPalette<TPixel>(TestImageProvider<TPixel> provider)
100+
where TPixel : unmanaged, IPixel<TPixel>
101+
{
102+
if (TestEnvironment.IsWindows)
103+
{
104+
TestTiffDecoder(provider, new SystemDrawingReferenceDecoder(), useExactComparer: false, 0.01f);
105+
}
106+
}
107+
94108
[Theory]
95109
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
96110
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]
@@ -155,12 +169,15 @@ public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)
155169
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder);
156170
}
157171

158-
private static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider)
172+
private static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
159173
where TPixel : unmanaged, IPixel<TPixel>
160174
{
161175
using Image<TPixel> image = provider.GetImage(TiffDecoder);
162176
image.DebugSave(provider);
163-
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
177+
image.CompareToOriginal(
178+
provider,
179+
useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance),
180+
referenceDecoder ?? ReferenceDecoder);
164181
}
165182
}
166183
}

tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,12 @@ public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixe
296296

297297
[Theory]
298298
[WithFile(Rgb4BitPalette, PixelTypes.Rgba32)]
299+
[WithFile(Flower4BitPalette, PixelTypes.Rgba32)]
300+
[WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)]
299301
public void TiffEncoder_EncodeColorPalette_With4Bit_Works<TPixel>(TestImageProvider<TPixel> provider)
300302
where TPixel : unmanaged, IPixel<TPixel> =>
301303
//// Note: The magick reference decoder does not support 4 bit tiff's, so we use our TIFF decoder instead.
302-
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder());
304+
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f, imageDecoder: new TiffDecoder());
303305

304306
[Theory]
305307
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
@@ -460,7 +462,7 @@ private static void TestTiffEncoderCore<TPixel>(
460462
TiffCompression compression = TiffCompression.None,
461463
TiffPredictor predictor = TiffPredictor.None,
462464
bool useExactComparer = true,
463-
float compareTolerance = 0.01f,
465+
float compareTolerance = 0.001f,
464466
IImageDecoder imageDecoder = null)
465467
where TPixel : unmanaged, IPixel<TPixel>
466468
{

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ public static class Tiff
558558
public const string RgbPalette = "Tiff/rgb_palette.tiff";
559559
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
560560
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
561+
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff";
562+
public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff";
561563

562564
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
563565
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:18991fca75a89b3d15c7f93dee0454e3943920b595ba16145ebc1fd8bd45b1f5
3+
size 1905
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:700ec8103b4197c415ba90d983a7d5f471f155fd5b1c952d86ee9becba898a1a
3+
size 2010

0 commit comments

Comments
 (0)