mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.5 KiB
158 lines
5.5 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
using System.Buffers.Binary;
|
|
using System.Text;
|
|
using SixLabors.ImageSharp.Formats;
|
|
using SixLabors.ImageSharp.Formats.Png;
|
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
|
|
namespace SixLabors.ImageSharp.Tests.Formats.Png;
|
|
|
|
public partial class PngDecoderTests
|
|
{
|
|
[Fact]
|
|
public void Decode_IccLutExceedsVectorChannelCount_Throws()
|
|
{
|
|
byte[] profileData = BuildLut16Profile(3, 15, 2, 2, 2);
|
|
byte[] pngData = BuildPng(profileData);
|
|
DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Convert };
|
|
|
|
Assert.Throws<InvalidIccProfileException>(() => Image.Load(options, pngData));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Three-channel LUT conversion remains supported.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Decode_IccLutWithSupportedChannelCount_ConvertsPixels()
|
|
{
|
|
byte[] pngData = BuildPng(BuildLut16Profile(3, 3, 2, 2, 2));
|
|
DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Convert };
|
|
|
|
using Image<Rgb24> image = Image.Load<Rgb24>(options, pngData);
|
|
Assert.Equal(new Size(16, 16), image.Size);
|
|
|
|
// Every CLUT node contains a nonzero XYZ value, even though the encoded pixels are black.
|
|
Assert.NotEqual(default(Rgb24), image[0, 0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Preserving a profile does not impose the converter's four-component storage limit on the parser.
|
|
/// </summary>
|
|
/// <param name="outputChannels">The number of output channels in the LUT.</param>
|
|
[Theory]
|
|
[InlineData(3)]
|
|
[InlineData(15)]
|
|
public void Decode_IccLut_Preserve_RetainsChannels(int outputChannels)
|
|
{
|
|
byte[] pngData = BuildPng(BuildLut16Profile(3, outputChannels, 2, 2, 2));
|
|
DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Preserve };
|
|
|
|
using Image<Rgb24> image = Image.Load<Rgb24>(options, pngData);
|
|
IccLut16TagDataEntry entry = Assert.IsType<IccLut16TagDataEntry>(Assert.Single(image.Metadata.IccProfile.Entries));
|
|
Assert.Equal(outputChannels, entry.OutputValues.Length);
|
|
Assert.Equal(default(Rgb24), image[0, 0]);
|
|
}
|
|
|
|
private static byte[] BuildLut16Profile(int inputChannels, int outputChannels, int clutPoints, int inputTableLength, int outputTableLength)
|
|
{
|
|
using MemoryStream stream = new();
|
|
|
|
byte[] header = new byte[128];
|
|
BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(8), 0x04300000U);
|
|
Encoding.ASCII.GetBytes("mntr").CopyTo(header, 12);
|
|
Encoding.ASCII.GetBytes("RGB ").CopyTo(header, 16);
|
|
Encoding.ASCII.GetBytes("XYZ ").CopyTo(header, 20);
|
|
stream.Write(header);
|
|
|
|
WriteUInt32(1);
|
|
stream.Write(Encoding.ASCII.GetBytes("A2B0"));
|
|
long offsetPosition = stream.Position;
|
|
WriteUInt32(0);
|
|
long sizePosition = stream.Position;
|
|
WriteUInt32(0);
|
|
|
|
long tagStart = stream.Position;
|
|
stream.Write(Encoding.ASCII.GetBytes("mft2"));
|
|
WriteUInt32(0);
|
|
stream.WriteByte((byte)inputChannels);
|
|
stream.WriteByte((byte)outputChannels);
|
|
stream.WriteByte((byte)clutPoints);
|
|
stream.WriteByte(0);
|
|
|
|
for (int y = 0; y < 3; y++)
|
|
{
|
|
for (int x = 0; x < 3; x++)
|
|
{
|
|
WriteFix16(x == y ? 1D : 0D);
|
|
}
|
|
}
|
|
|
|
WriteUInt16((ushort)inputTableLength);
|
|
WriteUInt16((ushort)outputTableLength);
|
|
|
|
for (int channel = 0; channel < inputChannels; channel++)
|
|
{
|
|
for (int i = 0; i < inputTableLength; i++)
|
|
{
|
|
WriteUInt16((ushort)(i == 0 ? 0 : ushort.MaxValue));
|
|
}
|
|
}
|
|
|
|
int clutLength = (int)Math.Pow(clutPoints, inputChannels);
|
|
for (int i = 0; i < clutLength; i++)
|
|
{
|
|
for (int channel = 0; channel < outputChannels; channel++)
|
|
{
|
|
WriteUInt16(0x8000);
|
|
}
|
|
}
|
|
|
|
for (int channel = 0; channel < outputChannels; channel++)
|
|
{
|
|
for (int i = 0; i < outputTableLength; i++)
|
|
{
|
|
WriteUInt16((ushort)(i == 0 ? 0 : ushort.MaxValue));
|
|
}
|
|
}
|
|
|
|
long tagEnd = stream.Position;
|
|
byte[] result = stream.ToArray();
|
|
BinaryPrimitives.WriteUInt32BigEndian(result.AsSpan((int)offsetPosition), (uint)tagStart);
|
|
BinaryPrimitives.WriteUInt32BigEndian(result.AsSpan((int)sizePosition), (uint)(tagEnd - tagStart));
|
|
BinaryPrimitives.WriteUInt32BigEndian(result, (uint)result.Length);
|
|
return result;
|
|
|
|
void WriteUInt32(uint value)
|
|
{
|
|
Span<byte> buffer = stackalloc byte[4];
|
|
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
|
|
stream.Write(buffer);
|
|
}
|
|
|
|
void WriteUInt16(ushort value)
|
|
{
|
|
Span<byte> buffer = stackalloc byte[2];
|
|
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
|
|
stream.Write(buffer);
|
|
}
|
|
|
|
void WriteFix16(double value)
|
|
{
|
|
int rawValue = (int)Math.Round(value * 65536D);
|
|
WriteUInt32(unchecked((uint)rawValue));
|
|
}
|
|
}
|
|
|
|
private static byte[] BuildPng(byte[] profileData)
|
|
{
|
|
using Image<Rgb24> image = new(16, 16);
|
|
image.Metadata.IccProfile = new IccProfile(profileData);
|
|
|
|
using MemoryStream stream = new();
|
|
image.SaveAsPng(stream);
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|