Browse Source

Merge branch 'master' into custom-app13-bugfix

pull/1931/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
fab1691e2c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  2. 76
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  3. 63
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  4. 31
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  5. 31
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

30
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -167,18 +167,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcusPerLine = this.frame.McusPerLine;
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks.
for (int i = 0; i < this.componentsCount; i++)
{
int order = this.frame.ComponentOrder[i];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure();
acHuffmanTable.Configure();
}
for (int j = 0; j < mcusPerColumn; j++)
{
this.cancellationToken.ThrowIfCancellationRequested();
@ -248,8 +236,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure();
acHuffmanTable.Configure();
for (int j = 0; j < h; j++)
{
@ -347,15 +333,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcusPerLine = this.frame.McusPerLine;
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks.
for (int k = 0; k < this.componentsCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure();
}
for (int j = 0; j < mcusPerColumn; j++)
{
for (int i = 0; i < mcusPerLine; i++)
@ -416,7 +393,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (this.SpectralStart == 0)
{
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure();
for (int j = 0; j < h; j++)
{
@ -444,7 +420,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
else
{
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
acHuffmanTable.Configure();
for (int j = 0; j < h; j++)
{
@ -752,11 +727,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="index">Table index.</param>
/// <param name="codeLengths">Code lengths.</param>
/// <param name="values">Code values.</param>
/// <param name="workspace">The provided spare workspace memory, can be dirty.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values, Span<uint> workspace)
{
HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[index] = new HuffmanTable(codeLengths, values);
tables[index] = new HuffmanTable(codeLengths, values, workspace);
}
}
}

76
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -13,12 +13,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct HuffmanTable
{
private bool isConfigured;
/// <summary>
/// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused.
/// Memory workspace buffer size used in <see cref="HuffmanTable"/> ctor.
/// </summary>
public fixed byte Sizes[17];
public const int WorkspaceByteSize = 256 * sizeof(uint);
/// <summary>
/// Derived from the DHT marker. Contains the symbols, in order of incremental code length.
@ -58,51 +56,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary>
/// <param name="codeLengths">The code lengths</param>
/// <param name="values">The huffman values</param>
public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
/// <param name="codeLengths">The code lengths.</param>
/// <param name="values">The huffman values.</param>
/// <param name="workspace">The provided spare workspace memory, can be dirty.</param>
public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values, Span<uint> workspace)
{
this.isConfigured = false;
Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
}
/// <summary>
/// Expands the HuffmanTable into its readable form.
/// </summary>
public void Configure()
{
if (this.isConfigured)
{
return;
}
Span<char> huffSize = stackalloc char[257];
Span<uint> huffCode = stackalloc uint[257];
// Figure C.1: make table of Huffman code length for each symbol
// Generate codes
uint code = 0;
int si = 1;
int p = 0;
for (int j = 1; j <= 16; j++)
for (int i = 1; i <= 16; i++)
{
int i = this.Sizes[j];
while (i-- != 0)
int count = codeLengths[i];
for (int j = 0; j < count; j++)
{
huffSize[p++] = (char)j;
workspace[p++] = code;
code++;
}
}
huffSize[p] = (char)0;
// Figure C.2: generate the codes themselves
uint code = 0;
int si = huffSize[0];
p = 0;
while (huffSize[p] != 0)
{
while (huffSize[p] == si)
// 'code' is now 1 more than the last code used for codelength 'si'
// in the valid worst possible case 'code' would have the least
// significant bit set to 1, e.g. 1111(0) +1 => 1111(1)
// but it must still fit in 'si' bits since no huffman code can be equal to all 1s
// if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield
// a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0)
if (code >= (1 << si))
{
huffCode[p++] = code;
code++;
JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table.");
}
code <<= 1;
@ -113,11 +95,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
p = 0;
for (int j = 1; j <= 16; j++)
{
if (this.Sizes[j] != 0)
if (codeLengths[j] != 0)
{
this.ValOffset[j] = p - (int)huffCode[p];
p += this.Sizes[j];
this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l
this.ValOffset[j] = p - (int)workspace[p];
p += codeLengths[j];
this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l
this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify
this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1;
}
@ -142,11 +124,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++)
{
int jShift = JpegConstants.Huffman.LookupBits - length;
for (int i = 1; i <= this.Sizes[length]; i++, p++)
for (int i = 1; i <= codeLengths[length]; i++, p++)
{
// length = current code's length, p = its index in huffCode[] & Values[].
// Generate left-justified code followed by all possible bit sequences
int lookBits = (int)(huffCode[p] << jShift);
int lookBits = (int)(workspace[p] << jShift);
for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
{
this.LookaheadSize[lookBits] = (byte)length;
@ -155,8 +137,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
}
this.isConfigured = true;
}
}
}

63
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -1100,12 +1100,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining)
{
int length = remaining;
const int codeLengthsByteSize = 17;
const int codeValuesMaxByteSize = 256;
const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize;
using (IMemoryOwner<byte> huffmanData = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
int length = remaining;
using (IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(totalBufferSize))
{
Span<byte> huffmanDataSpan = huffmanData.GetSpan();
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan);
Span<byte> bufferSpan = buffer.GetSpan();
Span<byte> huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
Span<byte> huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize);
Span<uint> tableWorkspace = MemoryMarshal.Cast<byte, uint>(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize));
for (int i = 2; i < remaining;)
{
byte huffmanTableSpec = (byte)stream.ReadByte();
@ -1115,49 +1121,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Types 0..1 DC..AC
if (tableType > 1)
{
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}");
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}.");
}
// Max tables of each type
if (tableIndex > 3)
{
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}");
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}.");
}
stream.Read(huffmanDataSpan, 0, 16);
stream.Read(huffmanLegthsSpan, 1, 16);
using (IMemoryOwner<byte> codeLengths = this.Configuration.MemoryAllocator.Allocate<byte>(17, AllocationOptions.Clean))
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
{
Span<byte> codeLengthsSpan = codeLengths.GetSpan();
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan);
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
{
codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1);
}
codeLengthSum += huffmanLegthsSpan[j];
}
length -= 17;
length -= 17;
if (codeLengthSum > 256 || codeLengthSum > length)
{
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
}
if (codeLengthSum > 256 || codeLengthSum > length)
{
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
}
using (IMemoryOwner<byte> huffmanValues = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
{
Span<byte> huffmanValuesSpan = huffmanValues.GetSpan();
stream.Read(huffmanValuesSpan, 0, codeLengthSum);
stream.Read(huffmanValuesSpan, 0, codeLengthSum);
i += 17 + codeLengthSum;
i += 17 + codeLengthSum;
this.scanDecoder.BuildHuffmanTable(
tableType,
tableIndex,
codeLengthsSpan,
huffmanValuesSpan);
}
}
this.scanDecoder.BuildHuffmanTable(
tableType,
tableIndex,
huffmanLegthsSpan,
huffmanValuesSpan.Slice(0, codeLengthSum),
tableWorkspace);
}
}
}

31
src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs

@ -46,23 +46,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
public byte[] GetData()
{
const uint startIndex = 0;
uint length;
IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset);
IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset);
if (this.ifdValues.Count == 0 && this.exifValues.Count == 0 && this.gpsValues.Count == 0)
{
return Array.Empty<byte>();
}
uint ifdLength = this.GetLength(this.ifdValues) + 4U;
uint ifdLength = this.GetLength(this.ifdValues);
uint exifLength = this.GetLength(this.exifValues);
uint gpsLength = this.GetLength(this.gpsValues);
length = ifdLength + exifLength + gpsLength;
uint length = ifdLength + exifLength + gpsLength;
if (length == 4U)
if (length == 0)
{
return Array.Empty<byte>();
}
@ -70,9 +64,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
// two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total
length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length;
length += 4 + 2;
// first IFD offset
length += 4;
var result = new byte[length];
byte[] result = new byte[length];
int i = 0;
@ -80,15 +75,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i));
i += ExifConstants.LittleEndianByteOrderMarker.Length;
uint ifdOffset = ((uint)i - startIndex) + 4U;
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
uint ifdOffset = (uint)i - startIndex + 4U;
exifOffset?.TrySetValue(ifdOffset + ifdLength);
gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength);
i = WriteUInt32(ifdOffset, result, i);
i = this.WriteHeaders(this.ifdValues, result, i);
i = WriteUInt32(thumbnailOffset, result, i);
i = this.WriteData(startIndex, this.ifdValues, result, i);
if (exifLength > 0)
@ -103,8 +96,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
i = this.WriteData(startIndex, this.gpsValues, result, i);
}
WriteUInt16(0, result, i);
return result;
}
@ -263,7 +254,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
uint valueLength = GetLength(value);
length += 2 + 2 + 4 + 4;
length += 12;
if (valueLength > 4)
{
@ -271,6 +262,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
// next IFD offset
length += 4;
return length;
}
@ -361,6 +355,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
newOffset += 4;
}
// next IFD offset
newOffset = WriteUInt32(0, destination, newOffset);
return newOffset;
}

31
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -86,6 +87,18 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
new ExifProfile(Array.Empty<byte>());
}
[Fact]
public void EmptyWriter()
{
var profile = new ExifProfile() { Parts = ExifParts.GpsTags };
profile.SetValue(ExifTag.Copyright, "Copyright text");
byte[] bytes = profile.ToByteArray();
Assert.NotNull(bytes);
Assert.Empty(bytes);
}
[Fact]
public void ConstructorCopy()
{
@ -420,7 +433,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime));
byte[] bytes = profile.ToByteArray();
Assert.Equal(525, bytes.Length);
Assert.Equal(531, bytes.Length);
var profile2 = new ExifProfile(bytes);
Assert.Equal(25, profile2.Values.Count);
@ -487,6 +500,22 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
return profile;
}
[Fact]
public void IfdStructure()
{
var exif = new ExifProfile();
exif.SetValue(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt"));
Span<byte> actualBytes = exif.ToByteArray();
// Assert
int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length;
Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4)));
int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12;
Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(nextIfdPointerOffset, 4)));
}
internal static ExifProfile GetExifProfile()
{
using Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();

Loading…
Cancel
Save