Browse Source

Merge remote-tracking branch 'upstream/master' into exif-encoded-strings

pull/1935/head
Ildar Khayrutdinov 4 years ago
parent
commit
b510cfb09d
  1. 82
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  2. 1
      src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
  3. 63
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  4. 103
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  5. 2
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  6. 10
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  7. 12
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  8. 30
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  9. 1
      tests/ImageSharp.Tests/TestImages.cs
  10. 3
      tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg

82
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -9,6 +9,10 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Memory;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Webp
{
@ -307,34 +311,94 @@ namespace SixLabors.ImageSharp.Formats.Webp
private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{
byte pred = (byte)(prev == null ? 0 : prev[0]);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0]));
if (width <= 1)
{
return;
}
nint i;
Vector128<int> last = Vector128<int>.Zero.WithElement(0, dst[0]);
ref byte srcRef = ref MemoryMarshal.GetReference(input);
for (i = 1; i + 8 <= width; i += 8)
{
var a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte());
Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1);
Vector128<byte> a3 = Sse2.Add(a1, a2);
Vector128<byte> a4 = Sse2.ShiftLeftLogical128BitLane(a3, 2);
Vector128<byte> a5 = Sse2.Add(a3, a4);
Vector128<byte> a6 = Sse2.ShiftLeftLogical128BitLane(a5, 4);
Vector128<byte> a7 = Sse2.Add(a5, a6);
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector64<byte>>(ref outputRef) = a7.GetLower();
last = Sse2.ShiftRightLogical(a7.AsInt64(), 56).AsInt32();
}
for (int i = 0; i < width; i++)
for (; i < width; ++i)
{
dst[(int)i] = (byte)(input[(int)i] + dst[(int)i - 1]);
}
}
else
#endif
{
byte val = (byte)(pred + input[i]);
pred = val;
dst[i] = val;
byte pred = (byte)(prev.IsEmpty ? 0 : prev[0]);
for (int i = 0; i < width; i++)
{
byte val = (byte)(pred + input[i]);
pred = val;
dst[i] = val;
}
}
}
private static void VerticalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{
if (prev == null)
if (prev.IsEmpty)
{
HorizontalUnfilter(null, input, dst, width);
}
else
{
for (int i = 0; i < width; i++)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
dst[i] = (byte)(prev[i] + input[i]);
nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
for (; i < width; i++)
{
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
}
else
#endif
{
for (int i = 0; i < width; i++)
{
dst[i] = (byte)(prev[i] + input[i]);
}
}
}
}
private static void GradientUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{
if (prev == null)
if (prev.IsEmpty)
{
HorizontalUnfilter(null, input, dst, width);
}

1
src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;

63
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -16,6 +16,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
private Collection<IptcValue> values;
private const byte IptcTagMarkerByte = 0x1c;
private const uint MaxStandardDataTagSize = 0x7FFF;
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
@ -78,7 +82,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
/// <inheritdoc/>
public IptcProfile DeepClone() => new IptcProfile(this);
public IptcProfile DeepClone() => new(this);
/// <summary>
/// Returns all values with the specified tag.
@ -207,7 +211,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
throw new ArgumentException("iptc tag is not a time or date type");
}
var formattedDate = tag.IsDate()
string formattedDate = tag.IsDate()
? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)
: dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
@ -231,7 +235,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </summary>
public void UpdateData()
{
var length = 0;
int length = 0;
foreach (IptcValue value in this.Values)
{
length += value.Length + 5;
@ -242,7 +246,24 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
int i = 0;
foreach (IptcValue value in this.Values)
{
this.Data[i++] = 28;
// Standard DataSet Tag
// +-----------+----------------+---------------------------------------------------------------------------------+
// | Octet Pos | Name | Description |
// +==========-+================+=================================================================================+
// | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. |
// +-----------+----------------+---------------------------------------------------------------------------------+
// | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the |
// | | | envelope record number is always 1, and that the application records are |
// | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record |
// | | | is 8, and the post - object descriptor record is 9. |
// +-----------+----------------+---------------------------------------------------------------------------------+
// | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. |
// +-----------+----------------+---------------------------------------------------------------------------------+
// | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in |
// | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of|
// | | | octet 4(most significant bit) always will be 0. |
// +-----------+----------------+---------------------------------------------------------------------------------+
this.Data[i++] = IptcTagMarkerByte;
this.Data[i++] = 2;
this.Data[i++] = (byte)value.Tag;
this.Data[i++] = (byte)(value.Length >> 8);
@ -264,34 +285,36 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.values = new Collection<IptcValue>();
if (this.Data == null || this.Data[0] != 0x1c)
if (this.Data == null || this.Data[0] != IptcTagMarkerByte)
{
return;
}
int i = 0;
while (i + 4 < this.Data.Length)
int offset = 0;
while (offset < this.Data.Length - 4)
{
if (this.Data[i++] != 28)
bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte;
byte recordNumber = this.Data[offset++];
bool isValidRecordNumber = recordNumber is >= 1 and <= 9;
var tag = (IptcTag)this.Data[offset++];
bool isValidEntry = isValidTagMarker && isValidRecordNumber;
uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2));
offset += 2;
if (byteCount > MaxStandardDataTagSize)
{
continue;
// Extended data set tag's are not supported.
break;
}
i++;
var tag = (IptcTag)this.Data[i++];
int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2));
i += 2;
var iptcData = new byte[count];
if ((count > 0) && (i + count <= this.Data.Length))
if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount))
{
Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
var iptcData = new byte[byteCount];
Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount);
this.values.Add(new IptcValue(tag, iptcData, false));
}
i += count;
offset += (int)byteCount;
}
}
}

103
src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs

@ -13,60 +13,57 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </summary>
/// <param name="tag">The tag to check the max length for.</param>
/// <returns>The maximum length.</returns>
public static int MaxLength(this IptcTag tag)
public static int MaxLength(this IptcTag tag) => tag switch
{
return tag switch
{
IptcTag.RecordVersion => 2,
IptcTag.ObjectType => 67,
IptcTag.ObjectAttribute => 68,
IptcTag.Name => 64,
IptcTag.EditStatus => 64,
IptcTag.EditorialUpdate => 2,
IptcTag.Urgency => 1,
IptcTag.SubjectReference => 236,
IptcTag.Category => 3,
IptcTag.SupplementalCategories => 32,
IptcTag.FixtureIdentifier => 32,
IptcTag.Keywords => 64,
IptcTag.LocationCode => 3,
IptcTag.LocationName => 64,
IptcTag.ReleaseDate => 8,
IptcTag.ReleaseTime => 11,
IptcTag.ExpirationDate => 8,
IptcTag.ExpirationTime => 11,
IptcTag.SpecialInstructions => 256,
IptcTag.ActionAdvised => 2,
IptcTag.ReferenceService => 10,
IptcTag.ReferenceDate => 8,
IptcTag.ReferenceNumber => 8,
IptcTag.CreatedDate => 8,
IptcTag.CreatedTime => 11,
IptcTag.DigitalCreationDate => 8,
IptcTag.DigitalCreationTime => 11,
IptcTag.OriginatingProgram => 32,
IptcTag.ProgramVersion => 10,
IptcTag.ObjectCycle => 1,
IptcTag.Byline => 32,
IptcTag.BylineTitle => 32,
IptcTag.City => 32,
IptcTag.SubLocation => 32,
IptcTag.ProvinceState => 32,
IptcTag.CountryCode => 3,
IptcTag.Country => 64,
IptcTag.OriginalTransmissionReference => 32,
IptcTag.Headline => 256,
IptcTag.Credit => 32,
IptcTag.Source => 32,
IptcTag.CopyrightNotice => 128,
IptcTag.Contact => 128,
IptcTag.Caption => 2000,
IptcTag.CaptionWriter => 32,
IptcTag.ImageType => 2,
IptcTag.ImageOrientation => 1,
_ => 256
};
}
IptcTag.RecordVersion => 2,
IptcTag.ObjectType => 67,
IptcTag.ObjectAttribute => 68,
IptcTag.Name => 64,
IptcTag.EditStatus => 64,
IptcTag.EditorialUpdate => 2,
IptcTag.Urgency => 1,
IptcTag.SubjectReference => 236,
IptcTag.Category => 3,
IptcTag.SupplementalCategories => 32,
IptcTag.FixtureIdentifier => 32,
IptcTag.Keywords => 64,
IptcTag.LocationCode => 3,
IptcTag.LocationName => 64,
IptcTag.ReleaseDate => 8,
IptcTag.ReleaseTime => 11,
IptcTag.ExpirationDate => 8,
IptcTag.ExpirationTime => 11,
IptcTag.SpecialInstructions => 256,
IptcTag.ActionAdvised => 2,
IptcTag.ReferenceService => 10,
IptcTag.ReferenceDate => 8,
IptcTag.ReferenceNumber => 8,
IptcTag.CreatedDate => 8,
IptcTag.CreatedTime => 11,
IptcTag.DigitalCreationDate => 8,
IptcTag.DigitalCreationTime => 11,
IptcTag.OriginatingProgram => 32,
IptcTag.ProgramVersion => 10,
IptcTag.ObjectCycle => 1,
IptcTag.Byline => 32,
IptcTag.BylineTitle => 32,
IptcTag.City => 32,
IptcTag.SubLocation => 32,
IptcTag.ProvinceState => 32,
IptcTag.CountryCode => 3,
IptcTag.Country => 64,
IptcTag.OriginalTransmissionReference => 32,
IptcTag.Headline => 256,
IptcTag.Credit => 32,
IptcTag.Source => 32,
IptcTag.CopyrightNotice => 128,
IptcTag.Contact => 128,
IptcTag.Caption => 2000,
IptcTag.CaptionWriter => 32,
IptcTag.ImageType => 2,
IptcTag.ImageOrientation => 1,
_ => 256
};
/// <summary>
/// Determines if the given tag can be repeated according to the specification.

2
src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs

@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
byte[] valueBytes;
if (this.Strict && value.Length > maxLength)
{
var cappedValue = value.Substring(0, maxLength);
string cappedValue = value.Substring(0, maxLength);
valueBytes = this.encoding.GetBytes(cappedValue);
// It is still possible that the bytes of the string exceed the limit.

10
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -85,11 +85,11 @@ namespace SixLabors.ImageSharp.Tests.Formats
public static readonly TheoryData<string> QuantizerNames =
new()
{
nameof(KnownQuantizers.Octree),
nameof(KnownQuantizers.WebSafe),
nameof(KnownQuantizers.Werner),
nameof(KnownQuantizers.Wu)
};
nameof(KnownQuantizers.Octree),
nameof(KnownQuantizers.WebSafe),
nameof(KnownQuantizers.Werner),
nameof(KnownQuantizers.Wu)
};
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)]

12
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -290,6 +290,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(72, imageInfo.Metadata.VerticalResolution);
});
[Theory]
[WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)]
public void Decode_WithInvalidIptcTag_DoesNotThrowException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
});
Assert.Null(ex);
}
[Fact]
public void ExifIfdStructure()
{

30
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
private static MagickReferenceDecoder ReferenceDecoder => new();
private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter);
private static string TestImageLossyVerticalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedVerticalFilter);
private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02);
private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter);
@ -365,6 +369,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
});
#if SUPPORTS_RUNTIME_INTRINSICS
private static void RunDecodeLossyWithHorizontalFilter()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyHorizontalFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
}
private static void RunDecodeLossyWithVerticalFilter()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyVerticalFilterPath);
using (Image<Rgba32> image = provider.GetImage(WebpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder);
}
}
private static void RunDecodeLossyWithSimpleFilterTest()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossySimpleFilterPath);
@ -385,6 +409,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
}
}
[Fact]
public void DecodeLossyWithHorizontalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithHorizontalFilter, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void DecodeLossyWithVerticalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithVerticalFilter, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic);

1
tests/ImageSharp.Tests/TestImages.cs

@ -261,6 +261,7 @@ namespace SixLabors.ImageSharp.Tests
public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg";
public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg";
public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg";
public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg";
public static class Fuzz
{

3
tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c9db428c4d9d7d1aea6778f263d8deaeeabdcfa63c77ef6ce36ab0e47b364dd
size 93374
Loading…
Cancel
Save