Browse Source

Merge branch 'main' into js/decoder-options

pull/2180/head
James Jackson-South 4 years ago
parent
commit
52a26d1639
  1. 101
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  2. 21
      src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs
  3. 8
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
  4. 16
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

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

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.IPTC;
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
@ -20,6 +21,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
private const uint MaxStandardDataTagSize = 0x7FFF;
/// <summary>
/// 1:90 Coded Character Set.
/// </summary>
private const byte IptcEnvelopeCodedCharacterSet = 0x5A;
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
@ -64,6 +70,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
}
/// <summary>
/// Gets a byte array marking that UTF-8 encoding is used in application records.
/// </summary>
private static ReadOnlySpan<byte> CodedCharacterSetUtf8Value => new byte[] { 0x1B, 0x25, 0x47 }; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs.
/// <summary>
/// Gets the byte data of the IPTC profile.
/// </summary>
@ -194,6 +205,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.values.Add(new IptcValue(tag, encoding, value, strict));
}
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary>
/// Makes sure the datetime is formatted according to the iptc specification.
/// <example>
@ -219,17 +241,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.SetValue(tag, Encoding.UTF8, formattedDate);
}
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary>
/// Updates the data of the profile.
/// </summary>
@ -241,12 +252,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
length += value.Length + 5;
}
bool hasValuesInUtf8 = this.HasValuesInUtf8();
if (hasValuesInUtf8)
{
// Additional length for UTF-8 Tag.
length += 5 + CodedCharacterSetUtf8Value.Length;
}
this.Data = new byte[length];
int offset = 0;
if (hasValuesInUtf8)
{
// Write Envelope Record.
offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet);
}
int i = 0;
foreach (IptcValue value in this.Values)
{
// Standard DataSet Tag
// Write Application Record.
// +-----------+----------------+---------------------------------------------------------------------------------+
// | Octet Pos | Name | Description |
// +==========-+================+=================================================================================+
@ -263,17 +287,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
// | | 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);
this.Data[i++] = (byte)value.Length;
if (value.Length > 0)
{
Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length);
i += value.Length;
}
offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag);
}
}
private int WriteRecord(int offset, ReadOnlySpan<byte> recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation)
{
Span<byte> data = this.Data.AsSpan(offset, 5);
data[0] = IptcTagMarkerByte;
data[1] = (byte)recordNumber;
data[2] = recordBinaryRepresentation;
data[3] = (byte)(recordData.Length >> 8);
data[4] = (byte)recordData.Length;
offset += 5;
if (recordData.Length > 0)
{
recordData.CopyTo(this.Data.AsSpan(offset));
offset += recordData.Length;
}
return offset;
}
private void Initialize()
@ -298,6 +331,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
bool isValidRecordNumber = recordNumber is >= 1 and <= 9;
var tag = (IptcTag)this.Data[offset++];
bool isValidEntry = isValidTagMarker && isValidRecordNumber;
bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application;
uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2));
offset += 2;
@ -307,9 +341,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
break;
}
if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount))
if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount))
{
var iptcData = new byte[byteCount];
byte[] iptcData = new byte[byteCount];
Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount);
this.values.Add(new IptcValue(tag, iptcData, false));
}
@ -317,5 +351,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
offset += (int)byteCount;
}
}
/// <summary>
/// Gets if any value has UTF-8 encoding.
/// </summary>
/// <returns>true if any value has UTF-8 encoding.</returns>
private bool HasValuesInUtf8()
{
foreach (IptcValue value in this.values)
{
if (value.Encoding == Encoding.UTF8)
{
return true;
}
}
return false;
}
}
}

21
src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC
{
/// <summary>
/// Enum for the different record types of a IPTC value.
/// </summary>
internal enum IptcRecordNumber : byte
{
/// <summary>
/// A Envelope Record.
/// </summary>
Envelope = 0x01,
/// <summary>
/// A Application Record.
/// </summary>
Application = 0x02
}
}

8
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs

@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
var expectedProfile = new IptcProfile();
expectedProfile.SetValue(IptcTag.Country, "ESPAÑA");
expectedProfile.SetValue(IptcTag.City, "unit-test-city");
input.Metadata.IptcProfile = expectedProfile;
// act
using var memStream = new MemoryStream();
@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values;
IEnumerable<IptcValue> values = expectedProfile.Values;
Assert.Equal(values, actual.Values);
}

16
tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

@ -27,6 +27,22 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
}
}
[Fact]
public void IptcProfile_WithUtf8Data_WritesEnvelopeRecord_Works()
{
// arrange
var profile = new IptcProfile();
profile.SetValue(IptcTag.City, "ESPAÑA");
profile.UpdateData();
byte[] expectedEnvelopeData = { 28, 1, 90, 0, 3, 27, 37, 71 };
// act
byte[] profileBytes = profile.Data;
// assert
Assert.True(profileBytes.AsSpan(0, 8).SequenceEqual(expectedEnvelopeData));
}
[Theory]
[MemberData(nameof(AllIptcTags))]
public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag)

Loading…
Cancel
Save