diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index b4e5c094ce..3c48488ecc 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -38,7 +38,7 @@ public static int FastAbs(int x) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetBitsNeededForColorDepth(int colors) { - return (int)Math.Ceiling(Math.Log(colors, 2)); + return Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); } /// diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 796a13a5e7..3b8aea6695 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -11,14 +11,20 @@ namespace SixLabors.ImageSharp.Formats.Png internal interface IPngEncoderOptions { /// - /// Gets the png color type + /// Gets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. /// - PngColorType PngColorType { get; } + PngBitDepth BitDepth { get; } /// - /// Gets the png filter method. + /// Gets the color type /// - PngFilterMethod PngFilterMethod { get; } + PngColorType ColorType { get; } + + /// + /// Gets the filter method. + /// + PngFilterMethod FilterMethod { get; } /// /// Gets the compression level 1-9. diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs new file mode 100644 index 0000000000..0c22a4c913 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// Note the value assignment, This will allow us to add 1, 2, and 4 bit encoding when we support it. +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides enumeration for the available PNG bit depths. + /// + public enum PngBitDepth + { + /// + /// 8 bits per sample or per palette index (not per pixel). + /// + Bit8 = 8, + + /// + /// 16 bits per sample or per palette index (not per pixel). + /// + Bit16 = 16 + } +} diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 0036280a83..64dad23bca 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Png public sealed class PngConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration config) + public void Configure(Configuration configuration) { - config.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder()); - config.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder()); - config.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); + configuration.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder()); + configuration.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 363d51ef9b..04d4f057ce 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; @@ -29,10 +28,10 @@ internal sealed class PngDecoderCore /// private static readonly Dictionary ColorTypes = new Dictionary() { - [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8 }, + [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, [PngColorType.Rgb] = new byte[] { 8, 16 }, [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, - [PngColorType.GrayscaleWithAlpha] = new byte[] { 8 }, + [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } }; @@ -162,14 +161,24 @@ internal sealed class PngDecoderCore private PngColorType pngColorType; /// - /// Represents any color in an Rgb24 encoded png that should be transparent + /// Represents any color in an 8 bit Rgb24 encoded png that should be transparent /// private Rgb24 rgb24Trans; /// - /// Represents any color in a grayscale encoded png that should be transparent + /// Represents any color in a 16 bit Rgb24 encoded png that should be transparent /// - private byte intensityTrans; + private Rgb48 rgb48Trans; + + /// + /// Represents any color in an 8 bit grayscale encoded png that should be transparent + /// + private byte luminanceTrans; + + /// + /// Represents any color in a 16 bit grayscale encoded png that should be transparent + /// + private ushort luminance16Trans; /// /// Whether the image has transparency chunk and markers were decoded @@ -332,24 +341,36 @@ public IImageInfo Identify(Stream stream) } /// - /// Converts a byte array to a new array where each value in the original array is represented by the specified number of bits. + /// Reads the least significant bits from the byte pair with the others set to 0. + /// + /// The source buffer + /// THe offset + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) + { + return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); + } + + /// + /// Attempts to convert a byte array to a new array where each value in the original array is represented by the + /// specified number of bits. /// /// The bytes to convert from. Cannot be empty. /// The number of bytes per scanline /// The number of bits per value. + /// The new array. /// The resulting array. - /// is less than or equals than zero. - private static ReadOnlySpan ToArrayByBitsLength(ReadOnlySpan source, int bytesPerScanline, int bits) + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) { - Guard.MustBeGreaterThan(source.Length, 0, nameof(source)); - Guard.MustBeGreaterThan(bits, 0, nameof(bits)); - if (bits >= 8) { - return source; + buffer = null; + return false; } - byte[] result = new byte[bytesPerScanline * 8 / bits]; + buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits); + byte[] result = buffer.Array; int mask = 0xFF >> (8 - bits); int resultOffset = 0; @@ -359,25 +380,13 @@ private static ReadOnlySpan ToArrayByBitsLength(ReadOnlySpan source, for (int shift = 0; shift < 8; shift += bits) { int colorIndex = (b >> (8 - bits - shift)) & mask; - result[resultOffset] = (byte)colorIndex; resultOffset++; } } - return result; - } - - /// - /// Reads an integer value from 2 consecutive bytes in LSB order - /// - /// The source buffer - /// THe offset - /// The - public static int ReadIntFrom2Bytes(byte[] buffer, int offset) - { - return ((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF); + return true; } /// @@ -445,30 +454,20 @@ private int CalculateBytesPerPixel() switch (this.pngColorType) { case PngColorType.Grayscale: - return 1; + return this.header.BitDepth == 16 ? 2 : 1; case PngColorType.GrayscaleWithAlpha: - return 2; + return this.header.BitDepth == 16 ? 4 : 2; case PngColorType.Palette: return 1; case PngColorType.Rgb: - if (this.header.BitDepth == 16) - { - return 6; - } - - return 3; + return this.header.BitDepth == 16 ? 6 : 3; case PngColorType.RgbWithAlpha: default: - if (this.header.BitDepth == 16) - { - return 8; - } - - return 4; + return this.header.BitDepth == 16 ? 8 : 4; } } @@ -532,9 +531,8 @@ private void DecodePixelData(Stream compressedStream, ImageFrame this.currentRowBytesRead = 0; Span scanlineSpan = this.scanline.GetSpan(); - var filterType = (FilterType)scanlineSpan[0]; - switch (filterType) + switch ((FilterType)scanlineSpan[0]) { case FilterType.None: break; @@ -607,9 +605,8 @@ private void DecodeInterlacedPixelData(Stream compressedStream, ImageFra Span scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); Span prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); - var filterType = (FilterType)scanSpan[0]; - switch (filterType) + switch ((FilterType)scanSpan[0]) { case FilterType.None: break; @@ -670,53 +667,131 @@ private void DecodeInterlacedPixelData(Stream compressedStream, ImageFra private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels) where TPixel : struct, IPixel { - TPixel color = default; + TPixel pixel = default; Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); // Trim the first marker byte from the buffer - ReadOnlySpan scanlineBuffer = defilteredScanline.Slice(1, defilteredScanline.Length - 1); + ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); + + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) + ? buffer.GetSpan() + : trimmed; switch (this.pngColorType) { case PngColorType.Grayscale: + int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - ReadOnlySpan newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); - for (int x = 0; x < this.header.Width; x++) + if (!this.hasTrans) { - byte intensity = (byte)(newScanline1[x] * factor); - if (this.hasTrans && intensity == this.intensityTrans) + if (this.header.BitDepth == 16) { - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, 0)); + Rgb48 rgb48 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.R = luminance; + rgb48.G = luminance; + rgb48.B = luminance; + pixel.PackFromRgb48(rgb48); + rowSpan[x] = pixel; + } } else { - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); + // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. + var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = 0; x < this.header.Width; x++) + { + byte luminance = (byte)(scanlineSpan[x] * factor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } + } + } + else + { + if (this.header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = 0; x < this.header.Width; x++) + { + byte luminance = (byte)(scanlineSpan[x] * factor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } } - - rowSpan[x] = color; } break; case PngColorType.GrayscaleWithAlpha: - for (int x = 0; x < this.header.Width; x++) + if (this.header.BitDepth == 16) { - int offset = x * this.bytesPerPixel; + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = 0; x < this.header.Width; x++) + { + int offset = x * this.bytesPerPixel; + byte luminance = scanlineSpan[offset]; + byte alpha = scanlineSpan[offset + this.bytesPerSample]; - byte intensity = scanlineBuffer[offset]; - byte alpha = scanlineBuffer[offset + this.bytesPerSample]; + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } } break; case PngColorType.Palette: - this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan); + this.ProcessScanlineFromPalette(scanlineSpan, rowSpan); break; @@ -726,54 +801,52 @@ private void ProcessDefilteredScanline(ReadOnlySpan defilteredScan { if (this.header.BitDepth == 16) { - int length = this.header.Width * 3; - using (IBuffer compressed = this.configuration.MemoryAllocator.Allocate(length)) + Rgb48 rgb48 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) { - // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed.GetSpan(), length); - PixelOperations.Instance.PackFromRgb24Bytes(compressed.GetSpan(), rowSpan, this.header.Width); + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); + pixel.PackFromRgb48(rgb48); + rowSpan[x] = pixel; } } else { - PixelOperations.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width); + PixelOperations.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, this.header.Width); } } else { if (this.header.BitDepth == 16) { - int length = this.header.Width * 3; - using (IBuffer compressed = this.configuration.MemoryAllocator.Allocate(length)) + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) { - // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed.GetSpan(), length); - - Span rgb24Span = MemoryMarshal.Cast(compressed.GetSpan()); - for (int x = 0; x < this.header.Width; x++) - { - ref Rgb24 rgb24 = ref rgb24Span[x]; - Rgba32 rgba32 = default; - rgba32.Rgb = rgb24; - rgba32.A = (byte)(rgb24.Equals(this.rgb24Trans) ? 0 : 255); - - color.PackFromRgba32(rgba32); - rowSpan[x] = color; - } + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); + + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; } } else { - ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineBuffer); + ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); for (int x = 0; x < this.header.Width; x++) { ref readonly Rgb24 rgb24 = ref rgb24Span[x]; Rgba32 rgba32 = default; rgba32.Rgb = rgb24; - rgba32.A = (byte)(rgb24.Equals(this.rgb24Trans) ? 0 : 255); + rgba32.A = rgb24.Equals(this.rgb24Trans) ? byte.MinValue : byte.MaxValue; - color.PackFromRgba32(rgba32); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; } } } @@ -784,108 +857,26 @@ private void ProcessDefilteredScanline(ReadOnlySpan defilteredScan if (this.header.BitDepth == 16) { - int length = this.header.Width * 4; - using (IBuffer compressed = this.configuration.MemoryAllocator.Allocate(length)) + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < this.header.Width; x++, o += 8) { - // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed.GetSpan(), length); - PixelOperations.Instance.PackFromRgba32Bytes(compressed.GetSpan(), rowSpan, this.header.Width); + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2)); + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; } } else { - PixelOperations.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width); + PixelOperations.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, this.header.Width); } break; } - } - - /// - /// Compresses the given span from 16bpp to 8bpp - /// - /// The source buffer - /// The target buffer - /// The target length - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void From16BitTo8Bit(ReadOnlySpan source, Span target, int length) - { - for (int i = 0, j = 0; i < length; i++, j += 2) - { - target[i] = (byte)((source[j + 1] << 8) + source[j]); - } - } - - /// - /// Decodes and assigns marker colors that identify transparent pixels in non indexed images - /// - /// The aplha tRNS array - private void AssignTransparentMarkers(byte[] alpha) - { - if (this.pngColorType == PngColorType.Rgb) - { - if (alpha.Length >= 6) - { - byte r = (byte)ReadIntFrom2Bytes(alpha, 0); - byte g = (byte)ReadIntFrom2Bytes(alpha, 2); - byte b = (byte)ReadIntFrom2Bytes(alpha, 4); - this.rgb24Trans = new Rgb24(r, g, b); - this.hasTrans = true; - } - } - else if (this.pngColorType == PngColorType.Grayscale) - { - if (alpha.Length >= 2) - { - this.intensityTrans = (byte)ReadIntFrom2Bytes(alpha, 0); - this.hasTrans = true; - } - } - } - - /// - /// Processes a scanline that uses a palette - /// - /// The type of pixel we are expanding to - /// The scanline - /// Thecurrent output image row - private void ProcessScanlineFromPalette(ReadOnlySpan defilteredScanline, Span row) - where TPixel : struct, IPixel - { - ReadOnlySpan newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); - ReadOnlySpan pal = MemoryMarshal.Cast(this.palette); - TPixel color = default; - Rgba32 rgba = default; - - if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) - { - // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha - // channel and we should try to read it. - for (int x = 0; x < this.header.Width; x++) - { - int index = newScanline[x]; - - rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - rgba.Rgb = pal[index]; - - color.PackFromRgba32(rgba); - row[x] = color; - } - } - else - { - rgba.A = 255; - for (int x = 0; x < this.header.Width; x++) - { - int index = newScanline[x]; - - rgba.Rgb = pal[index]; - - color.PackFromRgba32(rgba); - row[x] = color; - } - } + buffer?.Dispose(); } /// @@ -899,78 +890,157 @@ private void ProcessScanlineFromPalette(ReadOnlySpan defilteredSca private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, int pixelOffset = 0, int increment = 1) where TPixel : struct, IPixel { - TPixel color = default; + TPixel pixel = default; // Trim the first marker byte from the buffer - ReadOnlySpan scanlineBuffer = defilteredScanline.Slice(1, defilteredScanline.Length - 1); + ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); + + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) + ? buffer.GetSpan() + : trimmed; switch (this.pngColorType) { case PngColorType.Grayscale: + int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - ReadOnlySpan newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) + if (!this.hasTrans) + { + if (this.header.BitDepth == 16) + { + Rgb48 rgb48 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.R = luminance; + rgb48.G = luminance; + rgb48.B = luminance; + + pixel.PackFromRgb48(rgb48); + rowSpan[x] = pixel; + } + } + else + { + // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. + var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) + { + byte luminance = (byte)(scanlineSpan[o] * factor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } + } + } + else { - byte intensity = (byte)(newScanline1[o] * factor); - if (this.hasTrans && intensity == this.intensityTrans) + if (this.header.BitDepth == 16) { - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, 0)); + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; + } } else { - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); + Rgba32 rgba32 = default; + for (int x = pixelOffset; x < this.header.Width; x += increment) + { + byte luminance = (byte)(scanlineSpan[x] * factor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } } - - rowSpan[x] = color; } break; case PngColorType.GrayscaleWithAlpha: - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) + if (this.header.BitDepth == 16) { - byte intensity = scanlineBuffer[o]; - byte alpha = scanlineBuffer[o + this.bytesPerSample]; - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); - rowSpan[x] = color; + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = pixelOffset; x < this.header.Width; x += increment) + { + int offset = x * this.bytesPerPixel; + byte luminance = scanlineSpan[offset]; + byte alpha = scanlineSpan[offset + this.bytesPerSample]; + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; + + pixel.PackFromRgba32(rgba32); + rowSpan[x] = pixel; + } } break; case PngColorType.Palette: - ReadOnlySpan newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); - Rgba32 rgba = default; - Span pal = MemoryMarshal.Cast(this.palette); + Span palettePixels = MemoryMarshal.Cast(this.palette); - if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) + if (this.paletteAlpha?.Length > 0) { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. + Rgba32 rgba = default; for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { - int index = newScanline[o]; - - rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - rgba.Rgb = pal[index]; + int index = scanlineSpan[o]; + rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; + rgba.Rgb = palettePixels[index]; - color.PackFromRgba32(rgba); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba); + rowSpan[x] = pixel; } } else { - rgba.A = 255; - + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { - int index = newScanline[o]; + int index = scanlineSpan[o]; + rgba.Rgb = palettePixels[index]; - rgba.Rgb = pal[index]; - color.PackFromRgba32(rgba); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba); + rowSpan[x] = pixel; } } @@ -978,42 +1048,35 @@ private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defi case PngColorType.Rgb: - rgba.A = 255; - if (this.header.BitDepth == 16) { - int length = this.header.Width * 3; - using (IBuffer compressed = this.configuration.MemoryAllocator.Allocate(length)) + if (this.hasTrans) { - Span compressedSpan = compressed.GetSpan(); + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; - if (this.hasTrans) - { - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) - { - rgba.R = compressedSpan[o]; - rgba.G = compressedSpan[o + 1]; - rgba.B = compressedSpan[o + 2]; - rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255); - - color.PackFromRgba32(rgba); - rowSpan[x] = color; - } + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; } - else + } + else + { + Rgb48 rgb48 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) { - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) - { - rgba.R = compressedSpan[o]; - rgba.G = compressedSpan[o + 1]; - rgba.B = compressedSpan[o + 2]; - - color.PackFromRgba32(rgba); - rowSpan[x] = color; - } + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); + pixel.PackFromRgb48(rgb48); + rowSpan[x] = pixel; } } } @@ -1021,27 +1084,29 @@ private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defi { if (this.hasTrans) { + Rgba32 rgba = default; for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = scanlineBuffer[o]; - rgba.G = scanlineBuffer[o + this.bytesPerSample]; - rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; - rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255); + rgba.R = scanlineSpan[o]; + rgba.G = scanlineSpan[o + this.bytesPerSample]; + rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; + rgba.A = this.rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; - color.PackFromRgba32(rgba); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba); + rowSpan[x] = pixel; } } else { + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = scanlineBuffer[o]; - rgba.G = scanlineBuffer[o + this.bytesPerSample]; - rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; + rgba.R = scanlineSpan[o]; + rgba.G = scanlineSpan[o + this.bytesPerSample]; + rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; - color.PackFromRgba32(rgba); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba); + rowSpan[x] = pixel; } } } @@ -1052,71 +1117,125 @@ private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defi if (this.header.BitDepth == 16) { - int length = this.header.Width * 4; - using (IBuffer compressed = this.configuration.MemoryAllocator.Allocate(length)) + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 8) { - Span compressedSpan = compressed.GetSpan(); - - // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) - { - rgba.R = compressedSpan[o]; - rgba.G = compressedSpan[o + 1]; - rgba.B = compressedSpan[o + 2]; - rgba.A = compressedSpan[o + 3]; - - color.PackFromRgba32(rgba); - rowSpan[x] = color; - } + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2)); + pixel.PackFromRgba64(rgba64); + rowSpan[x] = pixel; } } else { + Rgba32 rgba = default; for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = scanlineBuffer[o]; - rgba.G = scanlineBuffer[o + this.bytesPerSample]; - rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; - rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)]; + rgba.R = scanlineSpan[o]; + rgba.G = scanlineSpan[o + this.bytesPerSample]; + rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; + rgba.A = scanlineSpan[o + (3 * this.bytesPerSample)]; - color.PackFromRgba32(rgba); - rowSpan[x] = color; + pixel.PackFromRgba32(rgba); + rowSpan[x] = pixel; } } break; } + + buffer?.Dispose(); } /// - /// Reads a text chunk containing image properties from the data. + /// Decodes and assigns marker colors that identify transparent pixels in non indexed images /// - /// The metadata to decode to. - /// The containing data. - /// The maximum length to read. - private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) + /// The aplha tRNS array + private void AssignTransparentMarkers(ReadOnlySpan alpha) { - if (this.ignoreMetadata) + if (this.pngColorType == PngColorType.Rgb) { - return; + if (alpha.Length >= 6) + { + if (this.header.BitDepth == 16) + { + ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)); + ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); + ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); + this.rgb48Trans = new Rgb48(rc, gc, bc); + this.hasTrans = true; + return; + } + + byte r = ReadByteLittleEndian(alpha, 0); + byte g = ReadByteLittleEndian(alpha, 2); + byte b = ReadByteLittleEndian(alpha, 4); + this.rgb24Trans = new Rgb24(r, g, b); + this.hasTrans = true; + } } + else if (this.pngColorType == PngColorType.Grayscale) + { + if (alpha.Length >= 2) + { + if (this.header.BitDepth == 16) + { + this.luminance16Trans = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)); + } + else + { + this.luminanceTrans = ReadByteLittleEndian(alpha, 0); + } - int zeroIndex = 0; + this.hasTrans = true; + } + } + } - for (int i = 0; i < length; i++) + /// + /// Processes a scanline that uses a palette + /// + /// The type of pixel we are expanding to + /// The defiltered scanline + /// Thecurrent output image row + private void ProcessScanlineFromPalette(ReadOnlySpan scanline, Span row) + where TPixel : struct, IPixel + { + ReadOnlySpan palettePixels = MemoryMarshal.Cast(this.palette); + var color = default(TPixel); + + if (this.paletteAlpha?.Length > 0) { - if (data[i] == 0) + Rgba32 rgba = default; + + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + for (int x = 0; x < this.header.Width; x++) { - zeroIndex = i; - break; + int index = scanline[x]; + rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; + rgba.Rgb = palettePixels[index]; + + color.PackFromRgba32(rgba); + row[x] = color; } } + else + { + // TODO: We should have PackFromRgb24. + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = 0; x < this.header.Width; x++) + { + int index = scanline[x]; - string name = this.textEncoding.GetString(data, 0, zeroIndex); - string value = this.textEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + rgba.Rgb = palettePixels[index]; - metadata.Properties.Add(new ImageProperty(name, value)); + color.PackFromRgba32(rgba); + row[x] = color; + } + } } /// @@ -1166,9 +1285,40 @@ private void ValidateHeader() this.pngColorType = this.header.ColorType; } + /// + /// Reads a text chunk containing image properties from the data. + /// + /// The metadata to decode to. + /// The containing data. + /// The maximum length to read. + private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) + { + if (this.ignoreMetadata) + { + return; + } + + int zeroIndex = 0; + + for (int i = 0; i < length; i++) + { + if (data[i] == 0) + { + zeroIndex = i; + break; + } + } + + string name = this.textEncoding.GetString(data, 0, zeroIndex); + string value = this.textEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + + metadata.Properties.Add(new ImageProperty(name, value)); + } + /// /// Reads a chunk from the stream. /// + /// The image format chunk. /// /// The . /// @@ -1224,6 +1374,10 @@ private bool TryReadChunk(out PngChunk chunk) return true; } + /// + /// Validates the png chunk. + /// + /// The . private void ValidateChunk(in PngChunk chunk) { this.crc.Reset(); @@ -1259,6 +1413,7 @@ private uint ReadChunkCrc() /// /// Skips the chunk data and the cycle redundancy chunk read from the data. /// + /// The image format chunk. private void SkipChunkDataAndCrc(in PngChunk chunk) { this.currentStream.Skip(chunk.Length); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index fab1b51850..babda2effc 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -14,14 +14,20 @@ namespace SixLabors.ImageSharp.Formats.Png public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { /// - /// Gets or sets the png color type. + /// Gets or sets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8; /// - /// Gets or sets the png filter method. + /// Gets or sets the color type. /// - public PngFilterMethod PngFilterMethod { get; set; } = PngFilterMethod.Adaptive; + public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the filter method. + /// + public PngFilterMethod FilterMethod { get; set; } = PngFilterMethod.Paeth; /// /// Gets or sets the compression level 1-9. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index cf869e68a6..45e8669d68 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -41,6 +41,16 @@ internal sealed class PngEncoderCore : IDisposable /// private readonly Crc32 crc = new Crc32(); + /// + /// The png bit depth + /// + private readonly PngBitDepth pngBitDepth; + + /// + /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. + /// + private readonly bool use16Bit; + /// /// The png color type. /// @@ -149,8 +159,10 @@ internal sealed class PngEncoderCore : IDisposable public PngEncoderCore(MemoryAllocator memoryAllocator, IPngEncoderOptions options) { this.memoryAllocator = memoryAllocator; - this.pngColorType = options.PngColorType; - this.pngFilterMethod = options.PngFilterMethod; + this.pngBitDepth = options.BitDepth; + this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16); + this.pngColorType = options.ColorType; + this.pngFilterMethod = options.FilterMethod; this.compressionLevel = options.CompressionLevel; this.gamma = options.Gamma; this.quantizer = options.Quantizer; @@ -197,7 +209,7 @@ public void Encode(Image image, Stream stream) } else { - this.bitDepth = 8; + this.bitDepth = (byte)(this.use16Bit ? 16 : 8); } this.bytesPerPixel = this.CalculateBytesPerPixel(); @@ -205,11 +217,11 @@ public void Encode(Image image, Stream stream) var header = new PngHeader( width: image.Width, height: image.Height, - colorType: this.pngColorType, bitDepth: this.bitDepth, - filterMethod: 0, // None - compressionMethod: 0, - interlaceMethod: 0); + colorType: this.pngColorType, + compressionMethod: 0, // None + filterMethod: 0, + interlaceMethod: 0); // TODO: Can't write interlaced yet. this.WriteHeaderChunk(stream, header); @@ -246,28 +258,62 @@ public void Dispose() private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) where TPixel : struct, IPixel { - byte[] rawScanlineArray = this.rawScanline.Array; - Rgba32 rgba = default; + // Use ITU-R recommendation 709 to match libpng. + const float RX = .2126F; + const float GX = .7152F; + const float BX = .0722F; + Span rawScanlineSpan = this.rawScanline.GetSpan(); - // Copy the pixels across from the image. - // Reuse the chunk type buffer. - for (int x = 0; x < this.width; x++) + if (this.pngColorType.Equals(PngColorType.Grayscale)) { - // Convert the color to YCbCr and store the luminance - // Optionally store the original color alpha. - int offset = x * this.bytesPerPixel; - rowSpan[x].ToRgba32(ref rgba); - byte luminance = (byte)((0.299F * rgba.R) + (0.587F * rgba.G) + (0.114F * rgba.B)); - - for (int i = 0; i < this.bytesPerPixel; i++) + // TODO: Realistically we should support 1, 2, 4, 8, and 16 bit grayscale images. + // we currently do the other types via palette. Maybe RC as I don't understand how the data is packed yet + // for 1, 2, and 4 bit grayscale images. + if (this.use16Bit) { - if (i == 0) + // 16 bit grayscale + Rgb48 rgb = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) { - rawScanlineArray[offset] = luminance; + rowSpan[x].ToRgb48(ref rgb); + ushort luminance = (ushort)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); } - else + } + else + { + // 8 bit grayscale + Rgb24 rgb = default; + for (int x = 0; x < rowSpan.Length; x++) { - rawScanlineArray[offset + i] = rgba.A; + rowSpan[x].ToRgb24(ref rgb); + rawScanlineSpan[x] = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)); + } + } + } + else + { + if (this.use16Bit) + { + // 16 bit grayscale + alpha + Rgba64 rgba = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 4) + { + rowSpan[x].ToRgba64(ref rgba); + ushort luminance = (ushort)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B)); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A); + } + } + else + { + // 8 bit grayscale + alpha + Rgba32 rgba = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) + { + rowSpan[x].ToRgba32(ref rgba); + rawScanlineSpan[o] = (byte)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B)); + rawScanlineSpan[o + 1] = rgba.A; } } } @@ -281,13 +327,54 @@ private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) private void CollectTPixelBytes(ReadOnlySpan rowSpan) where TPixel : struct, IPixel { - if (this.bytesPerPixel == 4) - { - PixelOperations.Instance.ToRgba32Bytes(rowSpan, this.rawScanline.GetSpan(), this.width); - } - else + Span rawScanlineSpan = this.rawScanline.GetSpan(); + + switch (this.bytesPerPixel) { - PixelOperations.Instance.ToRgb24Bytes(rowSpan, this.rawScanline.GetSpan(), this.width); + case 4: + { + // 8 bit Rgba + PixelOperations.Instance.ToRgba32Bytes(rowSpan, rawScanlineSpan, this.width); + break; + } + + case 3: + { + // 8 bit Rgb + PixelOperations.Instance.ToRgb24Bytes(rowSpan, rawScanlineSpan, this.width); + break; + } + + case 8: + { + // 16 bit Rgba + Rgba64 rgba = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) + { + rowSpan[x].ToRgba64(ref rgba); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A); + } + + break; + } + + default: + { + // 16 bit Rgb + Rgb48 rgb = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) + { + rowSpan[x].ToRgb48(ref rgb); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B); + } + + break; + } } } @@ -305,9 +392,10 @@ private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, switch (this.pngColorType) { case PngColorType.Palette: - int stride = this.rawScanline.Length(); + int stride = this.rawScanline.Length(); this.palettePixelData.AsSpan(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); + break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: @@ -366,6 +454,9 @@ private IManagedByteBuffer GetOptimalFilteredScanline() // early on which shaves a couple of milliseconds off the processing time. UpFilter.Encode(scanSpan, prevSpan, this.up.GetSpan(), out int currentSum); + // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. + // That way the above comment would actually be true. It used to be anyway... + // If we could use SIMD for none branching filters we could really speed it up. int lowestSum = currentSum; IManagedByteBuffer actualResult = this.up; @@ -404,22 +495,20 @@ private int CalculateBytesPerPixel() switch (this.pngColorType) { case PngColorType.Grayscale: - return 1; + return this.use16Bit ? 2 : 1; case PngColorType.GrayscaleWithAlpha: - return 2; + return this.use16Bit ? 4 : 2; case PngColorType.Palette: return 1; case PngColorType.Rgb: - return 3; + return this.use16Bit ? 6 : 3; // PngColorType.RgbWithAlpha - // TODO: Maybe figure out a way to detect if there are any transparent - // pixels and encode RGB if none. default: - return 4; + return this.use16Bit ? 8 : 4; } } @@ -557,12 +646,37 @@ private void WriteDataChunks(ImageFrame pixels, Stream stream) this.rawScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.result = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); - if (this.pngColorType != PngColorType.Palette) + switch (this.pngFilterMethod) { - this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); - this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); - this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); - this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + case PngFilterMethod.None: + break; + + case PngFilterMethod.Sub: + + this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + break; + + case PngFilterMethod.Up: + + this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + break; + + case PngFilterMethod.Average: + + this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + break; + + case PngFilterMethod.Paeth: + + this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + break; + case PngFilterMethod.Adaptive: + + this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + break; } byte[] buffer; diff --git a/src/ImageSharp/PixelFormats/Alpha8.cs b/src/ImageSharp/PixelFormats/Alpha8.cs index 659b2439f4..0b16fed0a5 100644 --- a/src/ImageSharp/PixelFormats/Alpha8.cs +++ b/src/ImageSharp/PixelFormats/Alpha8.cs @@ -155,6 +155,27 @@ public void ToBgra32(ref Bgra32 dest) dest.A = this.PackedValue; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) + { + dest.R = 0; + dest.G = 0; + dest.B = 0; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// /// Compares an object with the packed vector. /// diff --git a/src/ImageSharp/PixelFormats/Argb32.cs b/src/ImageSharp/PixelFormats/Argb32.cs index ef869af011..ccb17a2a5e 100644 --- a/src/ImageSharp/PixelFormats/Argb32.cs +++ b/src/ImageSharp/PixelFormats/Argb32.cs @@ -63,7 +63,7 @@ public Argb32(byte r, byte g, byte b) this.R = r; this.G = g; this.B = b; - this.A = 255; + this.A = byte.MaxValue; } /// @@ -310,6 +310,34 @@ public void ToBgra32(ref Bgra32 dest) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Argb32 ToArgb32() => this; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = (byte)(((source.A * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { @@ -336,8 +364,9 @@ public override string ToString() [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - // ReSharper disable once NonReadonlyMemberInGetHashCode - return this.Argb.GetHashCode(); + int hash = HashHelpers.Combine(this.R.GetHashCode(), this.G.GetHashCode()); + hash = HashHelpers.Combine(hash, this.B.GetHashCode()); + return HashHelpers.Combine(hash, this.A.GetHashCode()); } /// diff --git a/src/ImageSharp/PixelFormats/Bgr24.cs b/src/ImageSharp/PixelFormats/Bgr24.cs index ff913923e0..fc283b5684 100644 --- a/src/ImageSharp/PixelFormats/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/Bgr24.cs @@ -69,13 +69,8 @@ public override bool Equals(object obj) [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - unchecked - { - int hashCode = this.B; - hashCode = (hashCode * 397) ^ this.G; - hashCode = (hashCode * 397) ^ this.R; - return hashCode; - } + int hash = HashHelpers.Combine(this.R.GetHashCode(), this.G.GetHashCode()); + return HashHelpers.Combine(hash, this.B.GetHashCode()); } /// @@ -149,7 +144,7 @@ public void ToRgba32(ref Rgba32 dest) dest.R = this.R; dest.G = this.G; dest.B = this.B; - dest.A = 255; + dest.A = byte.MaxValue; } /// @@ -159,7 +154,7 @@ public void ToArgb32(ref Argb32 dest) dest.R = this.R; dest.G = this.G; dest.B = this.B; - dest.A = 255; + dest.A = byte.MaxValue; } /// @@ -174,7 +169,33 @@ public void ToBgra32(ref Bgra32 dest) dest.R = this.R; dest.G = this.G; dest.B = this.B; - dest.A = 255; + dest.A = byte.MaxValue; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Bgr565.cs b/src/ImageSharp/PixelFormats/Bgr565.cs index d1fa162e70..8595c6b9b1 100644 --- a/src/ImageSharp/PixelFormats/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/Bgr565.cs @@ -187,6 +187,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)MathF.Round(vector.W); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { @@ -223,9 +239,9 @@ public override int GetHashCode() [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z) { - return (ushort)((((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 11) | - (((int)Math.Round(y.Clamp(0, 1) * 63F) & 0x3F) << 5) | - ((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F)); + return (ushort)((((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 11) + | (((int)Math.Round(y.Clamp(0, 1) * 63F) & 0x3F) << 5) + | ((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F)); } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Bgra32.cs b/src/ImageSharp/PixelFormats/Bgra32.cs index de660c05b7..f951be8811 100644 --- a/src/ImageSharp/PixelFormats/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/Bgra32.cs @@ -60,7 +60,7 @@ public Bgra32(byte r, byte g, byte b) this.R = r; this.G = g; this.B = b; - this.A = 255; + this.A = byte.MaxValue; } /// @@ -113,14 +113,9 @@ public bool Equals(Bgra32 other) /// public override int GetHashCode() { - unchecked - { - int hashCode = this.B; - hashCode = (hashCode * 397) ^ this.G; - hashCode = (hashCode * 397) ^ this.R; - hashCode = (hashCode * 397) ^ this.A; - return hashCode; - } + int hash = HashHelpers.Combine(this.R.GetHashCode(), this.G.GetHashCode()); + hash = HashHelpers.Combine(hash, this.B.GetHashCode()); + return HashHelpers.Combine(hash, this.A.GetHashCode()); } /// @@ -219,17 +214,11 @@ public void ToArgb32(ref Argb32 dest) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgr24(ref Bgr24 dest) - { - dest = Unsafe.As(ref this); - } + public void ToBgr24(ref Bgr24 dest) => dest = Unsafe.As(ref this); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgra32(ref Bgra32 dest) - { - dest = this; - } + public void ToBgra32(ref Bgra32 dest) => dest = this; /// /// Converts the pixel to format. @@ -252,12 +241,41 @@ public void ToBgra32(ref Bgra32 dest) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Bgra32 ToBgra32() => this; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = (byte)(((source.A * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// /// Packs a into a color. /// /// The vector containing the values to pack. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Pack(ref Vector4 vector) { + private void Pack(ref Vector4 vector) + { vector *= MaxBytes; vector += Half; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); diff --git a/src/ImageSharp/PixelFormats/Bgra4444.cs b/src/ImageSharp/PixelFormats/Bgra4444.cs index 393723c850..b006aa5d2f 100644 --- a/src/ImageSharp/PixelFormats/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/Bgra4444.cs @@ -178,6 +178,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/Bgra5551.cs b/src/ImageSharp/PixelFormats/Bgra5551.cs index ba34412702..90a6251428 100644 --- a/src/ImageSharp/PixelFormats/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/Bgra5551.cs @@ -178,6 +178,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { @@ -222,10 +238,10 @@ public override int GetHashCode() private static ushort Pack(float x, float y, float z, float w) { return (ushort)( - (((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 10) | - (((int)Math.Round(y.Clamp(0, 1) * 31F) & 0x1F) << 5) | - (((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F) << 0) | - (((int)Math.Round(w.Clamp(0, 1)) & 0x1) << 15)); + (((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 10) + | (((int)Math.Round(y.Clamp(0, 1) * 31F) & 0x1F) << 5) + | (((int)Math.Round(z.Clamp(0, 1) * 31F) & 0x1F) << 0) + | (((int)Math.Round(w.Clamp(0, 1)) & 0x1) << 15)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Byte4.cs b/src/ImageSharp/PixelFormats/Byte4.cs index d91dac9ac9..4269557270 100644 --- a/src/ImageSharp/PixelFormats/Byte4.cs +++ b/src/ImageSharp/PixelFormats/Byte4.cs @@ -179,10 +179,26 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { - return (obj is Byte4) && this.Equals((Byte4)obj); + return obj is Byte4 byte4 && this.Equals(byte4); } /// diff --git a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs index 025204c894..f644fbefb5 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs @@ -11,6 +11,148 @@ namespace SixLabors.ImageSharp.PixelFormats public partial class PixelOperations { + /// + /// Converts 'count' elements in 'source` span of data to a span of -s. + /// + /// The source of data. + /// The to the destination pixels. + /// The number of pixels to convert. + internal virtual void PackFromRgba64(ReadOnlySpan source, Span destPixels, int count) + { + GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + + var rgba = new Rgba64(0, 0, 0, 65535); + + for (int i = 0; i < count; i++) + { + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + rgba = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgba64(rgba); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PackFromRgba64Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.PackFromRgba64(MemoryMarshal.Cast(sourceBytes), destPixels, count); + } + + /// + /// Converts 'count' pixels in 'sourcePixels` span to a span of -s. + /// Bulk version of . + /// + /// The span of source pixels + /// The destination span of data. + /// The number of pixels to convert. + internal virtual void ToRgba64(ReadOnlySpan sourcePixels, Span dest, int count) + { + GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < count; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destBaseRef, i); + sp.ToRgba64(ref dp); + } + } + + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgba64Bytes(ReadOnlySpan sourceColors, Span destBytes, int count) + { + this.ToRgba64(sourceColors, MemoryMarshal.Cast(destBytes), count); + } + + /// + /// Converts 'count' elements in 'source` span of data to a span of -s. + /// + /// The source of data. + /// The to the destination pixels. + /// The number of pixels to convert. + internal virtual void PackFromRgb48(ReadOnlySpan source, Span destPixels, int count) + { + GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + + var rgb = default(Rgb48); + + for (int i = 0; i < count; i++) + { + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + rgb = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgb48(rgb); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PackFromRgb48Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.PackFromRgb48(MemoryMarshal.Cast(sourceBytes), destPixels, count); + } + + /// + /// Converts 'count' pixels in 'sourcePixels` span to a span of -s. + /// Bulk version of . + /// + /// The span of source pixels + /// The destination span of data. + /// The number of pixels to convert. + internal virtual void ToRgb48(ReadOnlySpan sourcePixels, Span dest, int count) + { + GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < count; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destBaseRef, i); + sp.ToRgb48(ref dp); + } + } + + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgb48Bytes(ReadOnlySpan sourceColors, Span destBytes, int count) + { + this.ToRgb48(sourceColors, MemoryMarshal.Cast(destBytes), count); + } + /// /// Converts 'count' elements in 'source` span of data to a span of -s. /// diff --git a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt index 060973c744..1a6ac60f58 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt @@ -51,6 +51,90 @@ <# } + void GeneratePackFromMethodUsingPackFromRgba64(string pixelType, string rgbaOperationCode) + { + #> + + /// + /// Converts 'count' elements in 'source` span of data to a span of -s. + /// + /// The source of data. + /// The to the destination pixels. + /// The number of pixels to convert. + internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) + { + GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); + + ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + + var rgba = new Rgba64(0, 0, 0, 65535); + + for (int i = 0; i < count; i++) + { + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + <#=rgbaOperationCode#> + dp.PackFromRgba64(rgba); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); + } + <# + } + + void GeneratePackFromMethodUsingPackFromRgb48(string pixelType, string rgbaOperationCode) + { + #> + + /// + /// Converts 'count' elements in 'source` span of data to a span of -s. + /// + /// The source of data. + /// The to the destination pixels. + /// The number of pixels to convert. + internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) + { + GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); + + ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + + var rgb = default(Rgb48); + + for (int i = 0; i < count; i++) + { + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + <#=rgbaOperationCode#> + dp.PackFromRgb48(rgb); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); + } + <# + } + void GeneratePackFromMethodUsingPackFromRgba32(string pixelType, string rgbaOperationCode) { #> @@ -192,6 +276,12 @@ namespace SixLabors.ImageSharp.PixelFormats { <# + GeneratePackFromMethodUsingPackFromRgba64("Rgba64", "rgba = Unsafe.Add(ref sourceRef, i);"); + GenerateToDestFormatMethods("Rgba64"); + + GeneratePackFromMethodUsingPackFromRgb48("Rgb48", "rgb = Unsafe.Add(ref sourceRef, i);"); + GenerateToDestFormatMethods("Rgb48"); + GeneratePackFromMethodUsingPackFromRgba32("Rgba32", "rgba = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Rgba32"); diff --git a/src/ImageSharp/PixelFormats/HalfSingle.cs b/src/ImageSharp/PixelFormats/HalfSingle.cs index f85370ba1f..5049925421 100644 --- a/src/ImageSharp/PixelFormats/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/HalfSingle.cs @@ -192,6 +192,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/HalfVector2.cs b/src/ImageSharp/PixelFormats/HalfVector2.cs index acee34d6c0..72eb4f79cb 100644 --- a/src/ImageSharp/PixelFormats/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/HalfVector2.cs @@ -207,6 +207,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = 255; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override string ToString() { diff --git a/src/ImageSharp/PixelFormats/HalfVector4.cs b/src/ImageSharp/PixelFormats/HalfVector4.cs index 7c4cfb31ce..62a25bc2b8 100644 --- a/src/ImageSharp/PixelFormats/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/HalfVector4.cs @@ -200,6 +200,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override string ToString() { diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 7501cf3829..ae09af626c 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -61,6 +61,18 @@ public interface IPixel /// The value. void PackFromRgba32(Rgba32 source); + /// + /// Packs the pixel from an value. + /// + /// The value. + void PackFromRgb48(Rgb48 source); + + /// + /// Packs the pixel from an value. + /// + /// The value. + void PackFromRgba64(Rgba64 source); + /// /// Packs the pixel from an value. /// @@ -85,6 +97,18 @@ public interface IPixel /// The destination pixel to write to void ToRgba32(ref Rgba32 dest); + /// + /// Converts the pixel to format. + /// + /// The destination pixel to write to + void ToRgb48(ref Rgb48 dest); + + /// + /// Converts the pixel to format. + /// + /// The destination pixel to write to + void ToRgba64(ref Rgba64 dest); + /// /// Converts the pixel to format. /// diff --git a/src/ImageSharp/PixelFormats/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/NormalizedByte2.cs index 36ca11dd88..75a9075942 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte2.cs @@ -226,6 +226,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = 255; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/NormalizedByte4.cs index 8471285c79..fc3845eb28 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte4.cs @@ -219,6 +219,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/NormalizedShort2.cs index 6907594a06..2ddc83e763 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort2.cs @@ -213,6 +213,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = 255; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. diff --git a/src/ImageSharp/PixelFormats/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/NormalizedShort4.cs index 78c65212bc..25b26fa7f7 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort4.cs @@ -221,6 +221,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)MathF.Round(vector.W); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/Rg32.cs b/src/ImageSharp/PixelFormats/Rg32.cs index 696b823ce0..39a0ff4248 100644 --- a/src/ImageSharp/PixelFormats/Rg32.cs +++ b/src/ImageSharp/PixelFormats/Rg32.cs @@ -191,6 +191,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)vector.W; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/Rgb24.cs b/src/ImageSharp/PixelFormats/Rgb24.cs index faee3bbbd9..d7e1c47ec0 100644 --- a/src/ImageSharp/PixelFormats/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/Rgb24.cs @@ -70,13 +70,8 @@ public override bool Equals(object obj) [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - unchecked - { - int hashCode = this.R; - hashCode = (hashCode * 397) ^ this.G; - hashCode = (hashCode * 397) ^ this.B; - return hashCode; - } + int hash = HashHelpers.Combine(this.R.GetHashCode(), this.G.GetHashCode()); + return HashHelpers.Combine(hash, this.B.GetHashCode()); } /// @@ -131,21 +126,18 @@ public void PackFromVector4(Vector4 vector) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return new Rgba32(this.R, this.G, this.B, 255).ToVector4(); + return new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb24(ref Rgb24 dest) - { - dest = this; - } + public void ToRgb24(ref Rgb24 dest) => dest = this; /// public void ToRgba32(ref Rgba32 dest) { dest.Rgb = this; - dest.A = 255; + dest.A = byte.MaxValue; } /// @@ -154,7 +146,7 @@ public void ToArgb32(ref Argb32 dest) dest.R = this.R; dest.G = this.G; dest.B = this.B; - dest.A = 255; + dest.A = byte.MaxValue; } /// @@ -171,9 +163,35 @@ public void ToBgra32(ref Bgra32 dest) dest.R = this.R; dest.G = this.G; dest.B = this.B; - dest.A = 255; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override string ToString() { diff --git a/src/ImageSharp/PixelFormats/Rgb48.cs b/src/ImageSharp/PixelFormats/Rgb48.cs new file mode 100644 index 0000000000..2d92b0e4e3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Rgb48.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 635535. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public struct Rgb48 : IPixel + { + private const float Max = 65535F; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public Rgb48(ushort r, ushort g, ushort b) + : this() + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public Rgb48(float r, float g, float b) + : this() + { + this.R = (ushort)MathF.Round(r.Clamp(0, 1) * Max); + this.G = (ushort)MathF.Round(g.Clamp(0, 1) * Max); + this.B = (ushort)MathF.Round(b.Clamp(0, 1) * Max); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the components values. + public Rgb48(Vector3 vector) + : this(vector.X, vector.Y, vector.Z) + { + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb48 left, Rgb48 right) + { + return left.R == right.R + && left.G == right.G + && left.B == right.B; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb48 left, Rgb48 right) + { + return left.R != right.R + || left.G != right.G + || left.B != right.B; + } + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromScaledVector4(Vector4 vector) + { + this.PackFromVector4(vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() + { + return this.ToVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R / Max, this.G / Max, this.B / Max, 1); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.R = (ushort)MathF.Round(vector.X); + this.G = (ushort)MathF.Round(vector.Y); + this.B = (ushort)MathF.Round(vector.Z); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba32(Rgba32 source) + { + this.PackFromVector4(source.ToVector4()); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromArgb32(Argb32 source) + { + this.PackFromVector4(source.ToVector4()); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromBgra32(Bgra32 source) + { + this.PackFromVector4(source.ToVector4()); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this = source.Rgb; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb24(ref Rgb24 dest) + { + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToArgb32(ref Argb32 dest) + { + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgr24(ref Bgr24 dest) + { + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgra32(ref Bgra32 dest) + { + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = 255; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this = source; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest = this; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) + { + dest.Rgb = this; + dest.A = ushort.MaxValue; + } + + /// + public override bool Equals(object obj) + { + return obj is Rgb48 rgb48 && this.Equals(rgb48); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgb48 other) + { + return this.R == other.R + && this.G == other.G + && this.B == other.B; + } + + /// + public override string ToString() => this.ToVector4().ToString(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + { + return HashHelpers.Combine( + this.R.GetHashCode(), + HashHelpers.Combine(this.G.GetHashCode(), this.B.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Rgba1010102.cs b/src/ImageSharp/PixelFormats/Rgba1010102.cs index ee4865e4e1..94fb7a41e6 100644 --- a/src/ImageSharp/PixelFormats/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/Rgba1010102.cs @@ -185,6 +185,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)MathF.Round(vector.W); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { diff --git a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs index ca4231906f..2629ce3f79 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs @@ -87,15 +87,15 @@ internal static void ToVector4SimdAligned(ReadOnlySpan sourceColors, Spa } /// - internal override void ToVector4(ReadOnlySpan sourceColors, Span destVectors, int count) + internal override void ToVector4(ReadOnlySpan sourceColors, Span destinationVectors, int count) { Guard.MustBeSizedAtLeast(sourceColors, count, nameof(sourceColors)); - Guard.MustBeSizedAtLeast(destVectors, count, nameof(destVectors)); + Guard.MustBeSizedAtLeast(destinationVectors, count, nameof(destinationVectors)); if (count < 256 || !Vector.IsHardwareAccelerated) { // Doesn't worth to bother with SIMD: - base.ToVector4(sourceColors, destVectors, count); + base.ToVector4(sourceColors, destinationVectors, count); return; } @@ -104,25 +104,25 @@ internal override void ToVector4(ReadOnlySpan sourceColors, Span 0) { - ToVector4SimdAligned(sourceColors, destVectors, alignedCount); + ToVector4SimdAligned(sourceColors, destinationVectors, alignedCount); } if (remainder > 0) { sourceColors = sourceColors.Slice(alignedCount); - destVectors = destVectors.Slice(alignedCount); - base.ToVector4(sourceColors, destVectors, remainder); + destinationVectors = destinationVectors.Slice(alignedCount); + base.ToVector4(sourceColors, destinationVectors, remainder); } } /// - internal override void PackFromVector4(ReadOnlySpan sourceVectors, Span destColors, int count) + internal override void PackFromVector4(ReadOnlySpan sourceVectors, Span destinationColors, int count) { - GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count); + GuardSpans(sourceVectors, nameof(sourceVectors), destinationColors, nameof(destinationColors), count); if (!SimdUtils.IsAvx2CompatibleArchitecture) { - base.PackFromVector4(sourceVectors, destColors, count); + base.PackFromVector4(sourceVectors, destinationColors, count); return; } @@ -132,7 +132,7 @@ internal override void PackFromVector4(ReadOnlySpan sourceVectors, Span if (alignedCount > 0) { ReadOnlySpan flatSrc = MemoryMarshal.Cast(sourceVectors.Slice(0, alignedCount)); - Span flatDest = MemoryMarshal.Cast(destColors); + Span flatDest = MemoryMarshal.Cast(destinationColors); SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(flatSrc, flatDest); } @@ -141,7 +141,7 @@ internal override void PackFromVector4(ReadOnlySpan sourceVectors, Span { // actually: remainder == 1 int lastIdx = count - 1; - destColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]); + destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]); } } diff --git a/src/ImageSharp/PixelFormats/Rgba32.cs b/src/ImageSharp/PixelFormats/Rgba32.cs index bd014a58f5..79794ee462 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.cs @@ -83,7 +83,7 @@ public Rgba32(byte r, byte g, byte b) this.R = r; this.G = g; this.B = b; - this.A = 255; + this.A = byte.MaxValue; } /// @@ -199,7 +199,10 @@ public Bgr24 Bgr /// public uint PackedValue { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.Rgba; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this.Rgba = value; } @@ -384,10 +387,39 @@ public Vector4 ToVector4() [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rgba32 ToRgba32() => this; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) + { + // Taken from libpng pngtran.c line: 2419 + this.R = (byte)(((source.R * 255) + 32895) >> 16); + this.G = (byte)(((source.G * 255) + 32895) >> 16); + this.B = (byte)(((source.B * 255) + 32895) >> 16); + this.A = (byte)(((source.A * 255) + 32895) >> 16); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { - return obj is Rgba32 other && this.Equals(other); + return obj is Rgba32 rgba32 && this.Equals(rgba32); } /// @@ -397,10 +429,7 @@ public bool Equals(Rgba32 other) return this.Rgba == other.Rgba; } - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. + /// public override string ToString() { return $"({this.R},{this.G},{this.B},{this.A})"; @@ -410,8 +439,9 @@ public override string ToString() [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - // ReSharper disable once NonReadonlyMemberInGetHashCode - return this.Rgba.GetHashCode(); + int hash = HashHelpers.Combine(this.R.GetHashCode(), this.G.GetHashCode()); + hash = HashHelpers.Combine(hash, this.B.GetHashCode()); + return HashHelpers.Combine(hash, this.A.GetHashCode()); } /// diff --git a/src/ImageSharp/PixelFormats/Rgba64.cs b/src/ImageSharp/PixelFormats/Rgba64.cs index 6b891a7c08..b0aeab92ea 100644 --- a/src/ImageSharp/PixelFormats/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/Rgba64.cs @@ -4,27 +4,71 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { /// - /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. + /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 635535. /// /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. /// /// + [StructLayout(LayoutKind.Sequential)] public struct Rgba64 : IPixel, IPackedVector { + private const float Max = 65535F; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + + /// + /// Gets or sets the alpha component. + /// + public ushort A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba64(ushort r, ushort g, ushort b, ushort a) + : this() + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + /// /// Initializes a new instance of the struct. /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba64(float x, float y, float z, float w) + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba64(float r, float g, float b, float a) + : this() { - this.PackedValue = Pack(x, y, z, w); + this.R = (ushort)MathF.Round(r.Clamp(0, 1) * Max); + this.G = (ushort)MathF.Round(g.Clamp(0, 1) * Max); + this.B = (ushort)MathF.Round(b.Clamp(0, 1) * Max); + this.A = (ushort)MathF.Round(a.Clamp(0, 1) * Max); } /// @@ -32,12 +76,42 @@ public Rgba64(float x, float y, float z, float w) /// /// The vector containing the components values. public Rgba64(Vector4 vector) + : this(vector.X, vector.Y, vector.Z, vector.W) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The packed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba64(ulong packed) + : this() { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + this.PackedValue = packed; + } + + /// + /// Gets or sets the RGB components of this struct as + /// + public Rgb48 Rgb + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unsafe.As(ref this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; } /// - public ulong PackedValue { get; set; } + public ulong PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unsafe.As(ref this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } /// /// Compares two objects for equality. @@ -96,18 +170,18 @@ public Vector4 ToScaledVector4() [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return new Vector4( - (this.PackedValue & 0xFFFF) / 65535F, - ((this.PackedValue >> 16) & 0xFFFF) / 65535F, - ((this.PackedValue >> 32) & 0xFFFF) / 65535F, - ((this.PackedValue >> 48) & 0xFFFF) / 65535F); + return new Vector4(this.R, this.G, this.B, this.A) / Max; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.R = (ushort)MathF.Round(vector.X); + this.G = (ushort)MathF.Round(vector.Y); + this.B = (ushort)MathF.Round(vector.Z); + this.A = (ushort)MathF.Round(vector.W); } /// @@ -131,63 +205,79 @@ public void PackFromBgra32(Bgra32 source) this.PackFromVector4(source.ToVector4()); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this = source; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToRgb24(ref Rgb24 dest) { - Vector4 vector = this.ToVector4() * 255F; - dest.R = (byte)MathF.Round(vector.X); - dest.G = (byte)MathF.Round(vector.Y); - dest.B = (byte)MathF.Round(vector.Z); + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToRgba32(ref Rgba32 dest) { - Vector4 vector = this.ToVector4() * 255F; - dest.R = (byte)MathF.Round(vector.X); - dest.G = (byte)MathF.Round(vector.Y); - dest.B = (byte)MathF.Round(vector.Z); - dest.A = (byte)MathF.Round(vector.W); + // Taken from libpng pngtran.c line: 2419 + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = (byte)(((this.A * 255) + 32895) >> 16); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) + { + this.Rgb = source; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest = this.Rgb; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest = this; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToArgb32(ref Argb32 dest) { - Vector4 vector = this.ToVector4() * 255F; - dest.R = (byte)MathF.Round(vector.X); - dest.G = (byte)MathF.Round(vector.Y); - dest.B = (byte)MathF.Round(vector.Z); - dest.A = (byte)MathF.Round(vector.W); + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = (byte)(((this.A * 255) + 32895) >> 16); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToBgr24(ref Bgr24 dest) { - Vector4 vector = this.ToVector4() * 255F; - dest.R = (byte)MathF.Round(vector.X); - dest.G = (byte)MathF.Round(vector.Y); - dest.B = (byte)MathF.Round(vector.Z); + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToBgra32(ref Bgra32 dest) { - Vector4 vector = this.ToVector4() * 255F; - dest.R = (byte)MathF.Round(vector.X); - dest.G = (byte)MathF.Round(vector.Y); - dest.B = (byte)MathF.Round(vector.Z); - dest.A = (byte)MathF.Round(vector.W); + dest.R = (byte)(((this.R * 255) + 32895) >> 16); + dest.G = (byte)(((this.G * 255) + 32895) >> 16); + dest.B = (byte)(((this.B * 255) + 32895) >> 16); + dest.A = (byte)(((this.A * 255) + 32895) >> 16); } /// public override bool Equals(object obj) { - return obj is Rgba64 other && this.Equals(other); + return obj is Rgba64 rgba64 && this.Equals(rgba64); } /// @@ -209,22 +299,5 @@ public override int GetHashCode() { return this.PackedValue.GetHashCode(); } - - /// - /// Packs the components into a . - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - /// The containing the packed values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(float x, float y, float z, float w) - { - return (ulong)MathF.Round(x.Clamp(0, 1) * 65535F) | - ((ulong)MathF.Round(y.Clamp(0, 1) * 65535F) << 16) | - ((ulong)MathF.Round(z.Clamp(0, 1) * 65535F) << 32) | - ((ulong)MathF.Round(w.Clamp(0, 1) * 65535F) << 48); - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/RgbaVector.cs b/src/ImageSharp/PixelFormats/RgbaVector.cs index 6eaf69214c..dd5f77b80f 100644 --- a/src/ImageSharp/PixelFormats/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/RgbaVector.cs @@ -298,6 +298,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)MathF.Round(vector.W); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromScaledVector4(Vector4 vector) diff --git a/src/ImageSharp/PixelFormats/Short2.cs b/src/ImageSharp/PixelFormats/Short2.cs index abe653e881..c298c5a486 100644 --- a/src/ImageSharp/PixelFormats/Short2.cs +++ b/src/ImageSharp/PixelFormats/Short2.cs @@ -207,6 +207,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = 255; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. diff --git a/src/ImageSharp/PixelFormats/Short4.cs b/src/ImageSharp/PixelFormats/Short4.cs index d3bb891d94..5683ffceea 100644 --- a/src/ImageSharp/PixelFormats/Short4.cs +++ b/src/ImageSharp/PixelFormats/Short4.cs @@ -213,6 +213,22 @@ public void ToBgra32(ref Bgra32 dest) dest.A = (byte)MathF.Round(vector.W); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48(ref Rgb48 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromRgba64(Rgba64 source) => this.PackFromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + /// public override bool Equals(object obj) { @@ -279,8 +295,7 @@ private Vector4 ToByteScaledVector4() vector *= 255; vector += Half; vector += Round; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - return vector; + return Vector4.Clamp(vector, Vector4.Zero, MaxBytes); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs index 7c04d090be..0885dd183f 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs @@ -86,6 +86,7 @@ public void FontShapesAreRenderedCorrectly_LargeText( VerticalAlignment = VerticalAlignment.Top, HorizontalAlignment = HorizontalAlignment.Left, }; + TPixel color = NamedColors.Black; provider.VerifyOperation( diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 084b93b398..97b498ee4e 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -84,7 +84,7 @@ public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImagePr using (Image image = provider.GetImage()) { image.Mutate(c => c.Quantize(quantizer)); - image.DebugSave(provider, new PngEncoder() { PngColorType = PngColorType.Palette }, testOutputDetails: quantizerName); + image.DebugSave(provider, new PngEncoder() { ColorType = PngColorType.Palette }, testOutputDetails: quantizerName); } provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 6b3ef1dee8..41cc6db512 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -36,7 +36,7 @@ private ImageComparer GetImageComparer(TestImageProvider provide if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) { - bool baseline = file.ToLower().Contains("baseline"); + bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0; tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index f97e115b74..53f71fb7b9 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -20,79 +20,105 @@ public class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + // TODO: Cannot use exact comparer since System.Drawing doesn't preserve more than 32bits. + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.1302F, 2134); + // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. - private static byte[] raw1x1PngIHDRAndpHYs = + private static readonly byte[] raw1x1PngIHDRAndpHYs = { // PNG Identifier 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, - + // IHDR 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, // IHDR CRC - 0x90, 0x77, 0x53, 0xDE, + 0x90, 0x77, 0x53, 0xDE, // pHYS - 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, // pHYS CRC 0xC7, 0x6F, 0xA8, 0x64 }; // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. - private static byte[] raw1x1PngIDATAndIEND = + private static readonly byte[] raw1x1PngIDATAndIEND = { // IDAT 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x01, + 0x00, 0x04, 0x00, 0x01, + // IDAT CRC - 0x5C, 0xCD, 0xFF, 0x69, + 0x5C, 0xCD, 0xFF, 0x69, // IEND 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, - 0x4E, 0x44, + 0x4E, 0x44, + // IEND CRC 0xAE, 0x42, 0x60, 0x82 }; public static readonly string[] CommonTestImages = - { - TestImages.Png.Splash, - TestImages.Png.Indexed, - TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.Bad.CorruptedChunk, - - TestImages.Png.VimImage1, - TestImages.Png.VersioningImage1, - TestImages.Png.VersioningImage2, - - TestImages.Png.SnakeGame, - TestImages.Png.Banner7Adam7InterlaceMode, - TestImages.Png.Banner8Index, - - TestImages.Png.Bad.ChunkLength2, - TestImages.Png.VimImage2, - }; + { + TestImages.Png.Splash, + TestImages.Png.Indexed, + TestImages.Png.FilterVar, + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.CorruptedChunk, + + TestImages.Png.VimImage1, + TestImages.Png.VersioningImage1, + TestImages.Png.VersioningImage2, + + TestImages.Png.SnakeGame, + TestImages.Png.Banner7Adam7InterlaceMode, + TestImages.Png.Banner8Index, + TestImages.Png.Bad.ChunkLength2, + TestImages.Png.VimImage2, + + TestImages.Png.Rgb24BppTrans, + TestImages.Png.GrayAlpha8Bit + }; public static readonly string[] TestImages48Bpp = - { - TestImages.Png.Rgb48Bpp, - TestImages.Png.Rgb48BppInterlaced - }; + { + TestImages.Png.Rgb48Bpp, + TestImages.Png.Rgb48BppInterlaced + }; + + public static readonly string[] TestImages64Bpp = +{ + TestImages.Png.Rgba64Bpp, + TestImages.Png.Rgb48BppTrans + }; + + public static readonly string[] TestImagesGray16Bit = + { + TestImages.Png.Gray16Bit, + }; + + public static readonly string[] TestImagesGrayAlpha16Bit = + { + TestImages.Png.GrayAlpha16Bit, + TestImages.Png.GrayTrns16BitInterlaced + }; // This is a workaround for Mono-s decoder being incompatible with ours and GDI+. // We shouldn't mix these with the Interleaved cases (which are also failing with Mono System.Drawing). Let's go AAA! private static readonly string[] SkipOnMono = - { - TestImages.Png.Bad.ChunkLength2, - TestImages.Png.VimImage2, - TestImages.Png.Splash, - TestImages.Png.Indexed, - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.VersioningImage1, - TestImages.Png.Banner7Adam7InterlaceMode, - }; + { + TestImages.Png.Bad.ChunkLength2, + TestImages.Png.VimImage2, + TestImages.Png.Splash, + TestImages.Png.Indexed, + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.VersioningImage1, + TestImages.Png.Banner7Adam7InterlaceMode, + TestImages.Png.GrayTrns16BitInterlaced, + TestImages.Png.Rgb48BppInterlaced + }; private static bool SkipVerification(ITestImageProvider provider) { @@ -118,7 +144,7 @@ public void Decode(TestImageProvider provider) } } } - + [Theory] [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] public void Decode_Interlaced_DoesNotThrow(TestImageProvider provider) @@ -142,20 +168,66 @@ public void Decode_Interlaced_ImageIsCorrect(TestImageProvider p } } - // TODO: We need to decode these into Rgba64 properly, and do 'CompareToOriginal' in a Rgba64 mode! (See #285) [Theory] - [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)] public void Decode_48Bpp(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new PngDecoder())) { - image.DebugSave(provider); + var encoder = new PngEncoder { ColorType = PngColorType.Rgb, BitDepth = PngBitDepth.Bit16 }; - // Workaround a bug in mono-s System.Drawing PNG decoder. It can't deal with 48Bpp png-s :( - if (!TestEnvironment.IsLinux && !TestEnvironment.IsMono) + if (!SkipVerification(provider)) { - image.CompareToOriginal(provider, ImageComparer.Exact); + image.VerifyEncoder(provider, "png", null, encoder, customComparer: ValidatorComparer); + } + } + } + + [Theory] + [WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)] + public void Decode_64Bpp(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + var encoder = new PngEncoder { ColorType = PngColorType.RgbWithAlpha, BitDepth = PngBitDepth.Bit16 }; + + if (!SkipVerification(provider)) + { + image.VerifyEncoder(provider, "png", null, encoder, customComparer: ValidatorComparer); + } + } + } + + [Theory] + [WithFileCollection(nameof(TestImagesGray16Bit), PixelTypes.Rgb48)] + public void Decode_Gray16Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + var encoder = new PngEncoder { ColorType = PngColorType.Grayscale, BitDepth = PngBitDepth.Bit16 }; + + if (!SkipVerification(provider)) + { + image.VerifyEncoder(provider, "png", null, encoder, customComparer: ValidatorComparer); + } + } + } + + [Theory] + [WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)] + public void Decode_GrayAlpha16Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) + { + var encoder = new PngEncoder { ColorType = PngColorType.GrayscaleWithAlpha, BitDepth = PngBitDepth.Bit16 }; + + if (!SkipVerification(provider)) + { + image.VerifyEncoder(provider, "png", null, encoder, customComparer: ValidatorComparer); } } } @@ -233,7 +305,7 @@ public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] public void DetectPixelSize(string imagePath, int expectedPixelSize) { - TestFile testFile = TestFile.Create(imagePath); + var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); @@ -257,10 +329,7 @@ public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType var decoder = new PngDecoder(); - ImageFormatException exception = Assert.Throws(() => - { - decoder.Decode(null, memStream); - }); + ImageFormatException exception = Assert.Throws(() => decoder.Decode(null, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 11124ad030..eb046165d5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -130,8 +130,8 @@ private static void TestPngEncoderCore( var encoder = new PngEncoder { - PngColorType = pngColorType, - PngFilterMethod = pngFilterMethod, + ColorType = pngColorType, + FilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, Quantizer = new WuQuantizer(paletteSize) }; diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 0188824f9a..e7e2577a83 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -56,7 +56,4 @@ PreserveNewest - - - diff --git a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs index 56d6043a61..9a29236dba 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs @@ -157,5 +157,54 @@ public void Alpha8_PackFromScaledVector4_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Alpha8_PackFromScaledVector4_ToRgba64() + { + // arrange + Alpha8 alpha = default; + Rgba64 actual = default; + var expected = new Rgba64(0, 0, 0, 65535); + Vector4 scaled = new Alpha8(1F).ToScaledVector4(); + + // act + alpha.PackFromScaledVector4(scaled); + alpha.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Alpha8_PackFromRgb48_ToRgb48() + { + // arrange + var alpha = default(Alpha8); + var actual = default(Rgb48); + var expected = new Rgb48(0, 0, 0); + + // act + alpha.PackFromRgb48(expected); + alpha.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Alpha8_PackFromRgba64_ToRgba64() + { + // arrange + var alpha = default(Alpha8); + var actual = default(Rgba64); + var expected = new Rgba64(0, 0, 0, 65535); + + // act + alpha.PackFromRgba64(expected); + alpha.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index f432aca8a3..5817b5c329 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -189,5 +189,37 @@ public void Argb32_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Argb32_PackFromRgb48_ToRgb48() + { + // arrange + var argb = default(Argb32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + argb.PackFromRgb48(expected); + argb.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Argb32_PackFromRgba64_ToRgba64() + { + // arrange + var argb = default(Argb32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + argb.PackFromRgba64(expected); + argb.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index 33cdadb668..048a38380e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -22,13 +22,13 @@ public void Constructor(byte r, byte g, byte b) Assert.Equal(g, p.G); Assert.Equal(b, p.B); } - + [Fact] public unsafe void ByteLayoutIsSequentialBgr() { var color = new Bgr24(1, 2, 3); byte* ptr = (byte*)&color; - + Assert.Equal(3, ptr[0]); Assert.Equal(2, ptr[1]); Assert.Equal(1, ptr[2]); @@ -139,5 +139,37 @@ public void ToBgra32() Assert.Equal(new Bgra32(1, 2, 3, 255), bgra); } + + [Fact] + public void Bgr24_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Bgr24); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgr24_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Bgr24); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs index b1640c33de..b66cac9ca3 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -6,7 +6,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.PixelFormats -{ +{ public class Bgr565Tests { [Fact] @@ -144,5 +144,37 @@ public void Bgr565_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Bgr565_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Bgr565); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgr565_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Bgr565); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 71e04269df..70f8c35dfc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -12,7 +12,7 @@ public class Bgra32Tests public static readonly TheoryData ColorData = new TheoryData() { - { 1, 2, 3, 4 }, { 4, 5, 6, 7 }, { 0, 255, 42, 0 }, { 1, 2, 3, 255 } + { 1, 2, 3, 4 }, { 4, 5, 6, 7 }, { 0, 255, 42, 0 }, { 1, 2, 3, 255 } }; [Theory] @@ -146,5 +146,37 @@ public void ToBgra32() Assert.Equal(new Bgra32(1, 2, 3, 4), bgra); } + + [Fact] + public void Bgra32_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Bgra32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra32_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Bgra32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs index 07667220fd..f643d152ef 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -193,5 +193,37 @@ public void Bgra4444_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Bgra4444_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Bgra4444); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra4444_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Bgra4444); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index b446511965..b6a0780312 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -192,5 +192,37 @@ public void Bgra5551_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Bgra5551_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Bgra5551); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Bgra5551); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index b6c6232162..28555a7dff 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -190,5 +190,37 @@ public void Byte4_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Byte4_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Byte4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Byte4_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Byte4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 5507243dde..3376645f34 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -141,5 +141,37 @@ public void HalfSingle_Argb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void HalfSingle_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(HalfSingle); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfSingle_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(HalfSingle); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index fe806e0c93..c2a524f0dc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -146,5 +146,37 @@ public void HalfVector2_Argb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void HalfVector2_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(HalfVector2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector2_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(HalfVector2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 5744243a45..8b28dd8277 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -188,5 +188,37 @@ public void HalfVector4_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void HalfVector4_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(HalfVector4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector4_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(HalfVector4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index 418e89ecc4..83f22e2aa0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -157,5 +157,37 @@ public void NormalizedByte2_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedByte2_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(NormalizedByte2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedByte2_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(NormalizedByte2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 1a31b806b3..16516496f8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -168,5 +168,37 @@ public void NormalizedByte4_PackFromArgb32_ToRgba32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedByte4_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(NormalizedByte4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedByte4_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(NormalizedByte4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index fc952a55c8..2fb7f05aca 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -161,5 +161,37 @@ public void NormalizedShort2_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedShort2_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(NormalizedShort2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort2_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(NormalizedShort2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index ead301c569..7dcdd9c88b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -169,5 +169,37 @@ public void NormalizedShort4_PackFromArgb32_ToRgba32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void NormalizedShort4_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(NormalizedShort4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(NormalizedShort4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs index caab05279d..c0e69a9027 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -203,7 +204,7 @@ public void ToScaledVector4(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromXyzBytes(int count) + public void PackFromRgb24Bytes(int count) { byte[] source = CreateByteTestData(count * 3); var expected = new TPixel[count]; @@ -224,7 +225,7 @@ public void PackFromXyzBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void ToXyzBytes(int count) + public void ToRgb24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 3]; @@ -248,7 +249,7 @@ public void ToXyzBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromXyzwBytes(int count) + public void PackFromRgba32Bytes(int count) { byte[] source = CreateByteTestData(count * 4); var expected = new TPixel[count]; @@ -269,7 +270,7 @@ public void PackFromXyzwBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void ToXyzwBytes(int count) + public void ToRgba32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 4]; @@ -294,7 +295,109 @@ public void ToXyzwBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromZyxBytes(int count) + public void PackFromRgb48Bytes(int count) + { + byte[] source = CreateByteTestData(count * 6); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; + + var rgba64 = new Rgba64(0, 0, 0, 65535); + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + rgba64.Rgb = MemoryMarshal.Cast(sourceSpan.Slice(i6, 6))[0]; + expected[i].PackFromRgba64(rgba64); + } + + TestOperation( + source, + expected, + (s, d) => Operations.PackFromRgb48Bytes(s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgb48Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 6]; + Rgb48 rgb = default; + + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + source[i].ToRgb48(ref rgb); + Rgba64Bytes rgb48Bytes = Unsafe.As(ref rgb); + expected[i6] = rgb48Bytes[0]; + expected[i6 + 1] = rgb48Bytes[1]; + expected[i6 + 2] = rgb48Bytes[2]; + expected[i6 + 3] = rgb48Bytes[3]; + expected[i6 + 4] = rgb48Bytes[4]; + expected[i6 + 5] = rgb48Bytes[5]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToRgb48Bytes(s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromRgba64Bytes(int count) + { + byte[] source = CreateByteTestData(count * 8); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + expected[i].PackFromRgba64(MemoryMarshal.Cast(sourceSpan.Slice(i8, 8))[0]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.PackFromRgba64Bytes(s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgba64Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 8]; + Rgba64 rgba = default; + + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + source[i].ToRgba64(ref rgba); + Rgba64Bytes rgba64Bytes = Unsafe.As(ref rgba); + expected[i8] = rgba64Bytes[0]; + expected[i8 + 1] = rgba64Bytes[1]; + expected[i8 + 2] = rgba64Bytes[2]; + expected[i8 + 3] = rgba64Bytes[3]; + expected[i8 + 4] = rgba64Bytes[4]; + expected[i8 + 5] = rgba64Bytes[5]; + expected[i8 + 6] = rgba64Bytes[6]; + expected[i8 + 7] = rgba64Bytes[7]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToRgba64Bytes(s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromBgr24Bytes(int count) { byte[] source = CreateByteTestData(count * 3); var expected = new TPixel[count]; @@ -315,7 +418,7 @@ public void PackFromZyxBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void ToZyxBytes(int count) + public void ToBgr24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 3]; @@ -339,7 +442,7 @@ public void ToZyxBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromZyxwBytes(int count) + public void PackFromBgra32Bytes(int count) { byte[] source = CreateByteTestData(count * 4); var expected = new TPixel[count]; @@ -385,7 +488,7 @@ public void ToZyxwBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromWzyxBytes(int count) + public void PackFromArgb32Bytes(int count) { byte[] source = CreateByteTestData(count * 4); var expected = new TPixel[count]; @@ -406,7 +509,7 @@ public void PackFromWzyxBytes(int count) [Theory] [MemberData(nameof(ArraySizesData))] - public void ToWzyxBytes(int count) + public void ToArgb32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); byte[] expected = new byte[count * 4]; @@ -557,5 +660,21 @@ internal static Vector4 GetVector(Random rnd) (float)rnd.NextDouble() ); } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct Rgba64Bytes + { + public fixed byte Data[8]; + + public byte this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index cbecda8d52..3a80c3436d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -128,5 +128,37 @@ public void Rg32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Rg32_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rg32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rg32_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rg32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs index 5ba21096ea..d056bf30d2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs @@ -59,7 +59,7 @@ public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b Assert.False(a.Equals(b)); Assert.False(a.Equals((object)b)); } - + [Fact] public void PackFromRgba32() { @@ -139,5 +139,37 @@ public void ToBgra32() Assert.Equal(new Bgra32(1, 2, 3, 255), bgra); } + + [Fact] + public void Rgb24_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rgb24); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb24_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rgb24); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs new file mode 100644 index 0000000000..77d6544f00 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -0,0 +1,199 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class Rgb48Tests + { + [Fact] + public void Rgb48_Values() + { + var rgb = new Rgba64(5243, 9830, 19660, 29491); + + Assert.Equal(5243, rgb.R); + Assert.Equal(9830, rgb.G); + Assert.Equal(19660, rgb.B); + Assert.Equal(29491, rgb.A); + + rgb = new Rgba64(5243 / 65535F, 9830 / 65535F, 19660 / 65535F, 29491 / 65535F); + + Assert.Equal(5243, rgb.R); + Assert.Equal(9830, rgb.G); + Assert.Equal(19660, rgb.B); + Assert.Equal(29491, rgb.A); + } + + [Fact] + public void Rgb48_ToVector4() + { + Assert.Equal(new Vector4(0, 0, 0, 1), new Rgb48(Vector3.Zero).ToVector4()); + Assert.Equal(Vector4.One, new Rgb48(Vector3.One).ToVector4()); + } + + [Fact] + public void Rgb48_ToScaledVector4() + { + // arrange + var short2 = new Rgb48(Vector3.One); + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgb48_PackFromScaledVector4() + { + // arrange + var pixel = default(Rgb48); + var short3 = new Rgb48(Vector3.One); + var expected = new Rgb48(Vector3.One); + + // act + Vector4 scaled = short3.ToScaledVector4(); + pixel.PackFromScaledVector4(scaled); + + // assert + Assert.Equal(expected, pixel); + } + + [Fact] + public void Rgb48_Clamping() + { + Assert.Equal(new Vector4(0, 0, 0, 1), new Rgb48(Vector3.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Rgb48(Vector3.One * 1234.0f).ToVector4()); + } + + [Fact] + public void Rgb48_ToRgb24() + { + // arrange + var rgba48 = new Rgb48(0.08f, 0.15f, 0.30f); + var actual = default(Rgb24); + var expected = new Rgb24(20, 38, 76); + + // act + rgba48.ToRgb24(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_ToRgba32() + { + // arrange + var rgba48 = new Rgb48(0.08f, 0.15f, 0.30f); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 76, 255); + + // act + rgba48.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_ToArgb32() + { + // arrange + var rgba48 = new Rgb48(0.08f, 0.15f, 0.30f); + var actual = default(Argb32); + var expected = new Argb32(20, 38, 76, 255); + + // act + rgba48.ToArgb32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba64_ToBgr24() + { + // arrange + var rgb48 = new Rgb48(0.08f, 0.15f, 0.30f); + var actual = default(Bgr24); + var expected = new Bgr24(20, 38, 76); + + // act + rgb48.ToBgr24(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_ToBgra32() + { + // arrange + var rgba48 = new Rgb48(0.08f, 0.15f, 0.30f); + var actual = default(Bgra32); + var expected = new Bgra32(20, 38, 76, 255); + + // act + rgba48.ToBgra32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_PackFromRgba32_ToRgba32() + { + // arrange + var rgb48 = default(Rgb48); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 76, 255); + + // act + rgb48.PackFromRgba32(expected); + rgb48.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rgb48); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rgb48); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs index 361dd67b5c..fcb3b6c7c8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -178,5 +178,37 @@ public void Rgba1010102_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Rgba1010102_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rgba1010102); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba1010102_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rgba1010102); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index b8645d83ca..4b2c187765 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -305,5 +305,37 @@ public void Rgba32_PackFromArgb32_ToArgb32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Rgba32_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index ffe5128556..92b36a1c62 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -12,7 +12,14 @@ public class Rgba64Tests [Fact] public void Rgba64_PackedValues() { + Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(5243, 9830, 19660, 29491).PackedValue); Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(0.08f, 0.15f, 0.30f, 0.45f).PackedValue); + var rgba = new Rgba64(0x73334CCC2666147B); + Assert.Equal(5243, rgba.R); + Assert.Equal(9830, rgba.G); + Assert.Equal(19660, rgba.B); + Assert.Equal(29491, rgba.A); + // Test the limits. Assert.Equal((ulong)0x0, new Rgba64(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFFFFFFFFFF, new Rgba64(Vector4.One).PackedValue); @@ -50,7 +57,7 @@ public void Rgba64_PackFromScaledVector4() // arrange var pixel = default(Rgba64); var short4 = new Rgba64(Vector4.One); - ulong expected = 0xFFFFFFFFFFFFFFFF; + const ulong expected = 0xFFFFFFFFFFFFFFFF; // act Vector4 scaled = short4.ToScaledVector4(); @@ -98,6 +105,21 @@ public void Rgba64_ToRgba32() Assert.Equal(expected, actual); } + [Fact] + public void Rgba64_ToArgb32() + { + // arrange + var rgba64 = new Rgba64(0.08f, 0.15f, 0.30f, 0.45f); + var actual = default(Argb32); + var expected = new Argb32(20, 38, 76, 115); + + // act + rgba64.ToArgb32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void Rgba64_ToBgr24() { @@ -143,5 +165,37 @@ public void Rgba64_PackFromRgba32_ToRgba32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Rgb48_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Rgba64); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba64_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Rgba64); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index 1dd280bee4..a21b647a74 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { /// /// Tests the struct. @@ -126,5 +126,37 @@ public void FloatLayout() Assert.Equal(3, ordered[2]); Assert.Equal(4, ordered[3]); } + + [Fact] + public void RgbaVector_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void RgbaVector_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index b38c33b2a0..5c75fcbbbc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -169,5 +169,37 @@ public void Short2_PackFromRgba32_ToRgba32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Short2_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Short2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Short2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 097a533316..59dc385d4d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -205,5 +205,37 @@ public void Short4_PackFromArgb32_ToRgba32() // assert Assert.Equal(expected, actual); } + + [Fact] + public void Short4_PackFromRgb48_ToRgb48() + { + // arrange + var input = default(Short4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.PackFromRgb48(expected); + input.ToRgb48(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_PackFromRgba64_ToRgba64() + { + // arrange + var input = default(Short4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.PackFromRgba64(expected); + input.ToRgba64(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 6894f9b9bb..ae172a0bfe 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public class DetectEdgesTest : FileTestBase { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.001f); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 8a24046569..d275c1b1a4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [GroupOutput("Filters")] public class FilterTest { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3); // Testing the generic FilterProcessor with more than one pixel type intentionally. // There is no need to do this with the specialized ones. diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 3fc22264d6..6a6dc45f7c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -17,7 +17,7 @@ public class ResizeTests : FileTestBase { public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F); public static readonly TheoryData AllReSamplers = new TheoryData diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs index 1b06b0d6c7..fae876aff2 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs @@ -19,7 +19,7 @@ public class AffineTransformTests { private readonly ITestOutputHelper Output; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0085f, 3); /// /// angleDeg, sx, sy, tx, ty diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index b736dce207..089249e217 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -28,7 +28,7 @@ public class TestFile /// // ReSharper disable once InconsistentNaming private static readonly Lazy inputImagesDirectory = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); - + /// /// The image (lazy initialized value) /// @@ -74,9 +74,10 @@ private TestFile(string file) private Image Image => this.image ?? (this.image = ImageSharp.Image.Load(this.Bytes)); /// + /// Gets the input image directory. /// private static string InputImagesDirectory => inputImagesDirectory.Value; - + /// /// Gets the full qualified path to the input test file. /// diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d261f94974..6d3a76e75f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -27,12 +27,19 @@ public static class Png public const string Palette8Bpp = "Png/palette-8bpp.png"; public const string Bpp1 = "Png/bpp1.png"; public const string Gray4Bpp = "Png/gray_4bpp.png"; + public const string Gray16Bit = "Png/gray-16.png"; + public const string GrayAlpha8Bit = "Png/gray-alpha-8.png"; + public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; + public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; + public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; public const string Rgb48Bpp = "Png/rgb-48bpp.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; + public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png"; + public const string Rgba64Bpp = "Png/rgb-16-alpha.png"; public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; public const string Bike = "Png/Bike.png"; public const string BikeGrayscale = "Png/BikeGrayscale.png"; - public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; public const string SnakeGame = "Png/SnakeGame.png"; public const string Icon = "Png/icon.png"; public const string Kaboom = "Png/kaboom.png"; @@ -126,7 +133,7 @@ public static class Bad }; } - public class Issues + public static class Issues { public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index 5ed69f43d5..8dca11caeb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -22,10 +22,10 @@ public override ImageSimilarityReport CompareImagesOrFrames(); @@ -34,13 +34,13 @@ public override ImageSimilarityReport CompareImagesOrFrames aSpan = expected.GetPixelRowSpan(y); Span bSpan = actual.GetPixelRowSpan(y); - PixelOperations.Instance.ToRgba32(aSpan, aBuffer, width); - PixelOperations.Instance.ToRgba32(bSpan, bBuffer, width); + PixelOperations.Instance.ToRgba64(aSpan, aBuffer, width); + PixelOperations.Instance.ToRgba64(bSpan, bBuffer, width); for (int x = 0; x < width; x++) { - Rgba32 aPixel = aBuffer[x]; - Rgba32 bPixel = bBuffer[x]; + Rgba64 aPixel = aBuffer[x]; + Rgba64 bPixel = bBuffer[x]; if (aPixel != bPixel) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 8b0c3969ce..d000f70938 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -24,7 +24,7 @@ private static string StringifyReports(IEnumerable report int i = 0; foreach (ImageSimilarityReport r in reports) { - sb.Append($"Report{i}: "); + sb.Append($"Report ImageFrame {i}: "); sb.Append(r); sb.Append(Environment.NewLine); i++; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index bb5d0e6dd8..38dada063c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -28,9 +28,8 @@ public static ImageComparer Tolerant( /// /// Returns Tolerant(imageThresholdInPercents/100) /// - public static ImageComparer TolerantPercentage(float imageThresholdInPercents, - int perPixelManhattanThreshold = 0) => - Tolerant(imageThresholdInPercents / 100f, perPixelManhattanThreshold); + public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) + => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); public abstract ImageSimilarityReport CompareImagesOrFrames( ImageFrame expected, @@ -120,18 +119,20 @@ public static void VerifySimilarityIgnoreRegion( var cleanedReports = new List>(reports.Count()); foreach (ImageSimilarityReport r in reports) { - IEnumerable outsideChanges = r.Differences.Where(x => !( - ignoredRegion.X <= x.Position.X && - x.Position.X <= ignoredRegion.Right && - ignoredRegion.Y <= x.Position.Y && - x.Position.Y <= ignoredRegion.Bottom)); + IEnumerable outsideChanges = r.Differences.Where( + x => + !(ignoredRegion.X <= x.Position.X + && x.Position.X <= ignoredRegion.Right + && ignoredRegion.Y <= x.Position.Y + && x.Position.Y <= ignoredRegion.Bottom)); + if (outsideChanges.Any()) { cleanedReports.Add(new ImageSimilarityReport(r.ExpectedImage, r.ActualImage, outsideChanges, null)); } } - if (cleanedReports.Any()) + if (cleanedReports.Count > 0) { throw new ImageDifferenceIsOverThresholdException(cleanedReports); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 7465d61b86..f534079769 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -19,6 +19,7 @@ protected ImageSimilarityReport( this.TotalNormalizedDifference = totalNormalizedDifference; this.Differences = differences.ToArray(); } + public object ExpectedImage { get; } public object ActualImage { get; } @@ -59,6 +60,7 @@ private string PrintDifference() var sb = new StringBuilder(); if (this.TotalNormalizedDifference.HasValue) { + sb.AppendLine(); sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } int max = Math.Min(5, this.Differences.Length); @@ -68,7 +70,7 @@ private string PrintDifference() sb.Append(this.Differences[i]); if (i < max - 1) { - sb.Append("; "); + sb.AppendFormat(";{0}", Environment.NewLine); } } if (this.Differences.Length >= 5) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs index c1f79c619b..1ffeb60ad4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -19,12 +19,12 @@ public PixelDifference( this.AlphaDifference = alphaDifference; } - public PixelDifference(Point position, Rgba32 expected, Rgba32 actual) + public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) : this(position, - (int)actual.R - (int)expected.R, - (int)actual.G - (int)expected.G, - (int)actual.B - (int)expected.B, - (int)actual.A - (int)expected.A) + actual.R - expected.R, + actual.G - expected.G, + actual.B - expected.B, + actual.A - expected.A) { } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 667e90cfbd..674603380f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -12,11 +12,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public class TolerantImageComparer : ImageComparer { // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit - public const float DefaultImageThreshold = 1.0f / (100 * 100 * 255); + // 257 = (1 / 255) * 65535. + public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); /// /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. /// + /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Gets the threshold of the individual pixels before they acumulate towards the overall difference. public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) { Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); @@ -26,25 +29,28 @@ public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshol } /// - /// The maximal tolerated difference represented by a value between 0.0 and 1.0. + /// + /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. /// Examples of percentage differences on a single pixel: - /// 1. PixelA = (255,255,255,0) PixelB =(0,0,0,255) leads to 100% difference on a single pixel - /// 2. PixelA = (255,255,255,0) PixelB =(255,255,255,255) leads to 25% difference on a single pixel - /// 3. PixelA = (255,255,255,0) PixelB =(128,128,128,128) leads to 50% difference on a single pixel - /// + /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel + /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel + /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel + /// + /// /// The total differences is the sum of all pixel differences normalized by image dimensions! /// The individual distances are calculated using the Manhattan function: /// /// https://en.wikipedia.org/wiki/Taxicab_geometry /// - /// ImageThresholdInPercents = 1.0/255 means that we allow one byte difference per channel on a 1x1 image - /// ImageThresholdInPercents = 1.0/(100*100*255) means that we allow only one byte difference per channel on a 100x100 image + /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image + /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image + /// /// public float ImageThreshold { get; } /// - /// The threshold of the individual pixels before they acumulate towards the overall difference. - /// For an individual pixel pair the value is the Manhattan distance of pixels: + /// Gets the threshold of the individual pixels before they acumulate towards the overall difference. + /// For an individual pixel pair the value is the Manhattan distance of pixels: /// /// https://en.wikipedia.org/wiki/Taxicab_geometry /// @@ -60,12 +66,12 @@ public override ImageSimilarityReport CompareImagesOrFrames(); @@ -74,8 +80,8 @@ public override ImageSimilarityReport CompareImagesOrFrames aSpan = expected.GetPixelRowSpan(y); Span bSpan = actual.GetPixelRowSpan(y); - PixelOperations.Instance.ToRgba32(aSpan, aBuffer, width); - PixelOperations.Instance.ToRgba32(bSpan, bBuffer, width); + PixelOperations.Instance.ToRgba64(aSpan, aBuffer, width); + PixelOperations.Instance.ToRgba64(bSpan, bBuffer, width); for (int x = 0; x < width; x++) { @@ -91,8 +97,8 @@ public override ImageSimilarityReport CompareImagesOrFrames this.ImageThreshold) { @@ -105,12 +111,12 @@ public override ImageSimilarityReport CompareImagesOrFrames Math.Abs(a - b); + private static int Diff(ushort a, ushort b) => Math.Abs(a - b); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 4dcfcd4b78..9de791ab6d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -80,11 +80,12 @@ private static void VerticalBars(Buffer2D pixels) stride = 1; } - TPixel[] c = { - NamedColors.HotPink, - NamedColors.Blue - }; - + TPixel[] c = + { + NamedColors.HotPink, + NamedColors.Blue + }; + for (int y = top; y < bottom; y++) { int p = 0; @@ -112,10 +113,11 @@ private static void BlackWhiteChecker(Buffer2D pixels) int top = 0; int bottom = pixels.Height / 2; int stride = pixels.Width / 6; - TPixel[] c = { - NamedColors.Black, - NamedColors.White - }; + TPixel[] c = + { + NamedColors.Black, + NamedColors.White + }; int p = 0; for (int y = top; y < bottom; y++) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index bfd120fff5..2c4eb6c33c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -192,7 +192,7 @@ public IEnumerable GetTestOutputFileNamesMultiFrame( { Directory.CreateDirectory(baseDir); } - + for (int i = 0; i < frameCount; i++) { string filePath = $"{baseDir}/{i:D2}.{extension}"; @@ -258,7 +258,7 @@ internal void Init(string typeName, string methodName, string outputSubfolderNam this.TestName = methodName; this.OutputSubfolderName = outputSubfolderName; } - + internal string GetTestOutputDir() { string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); @@ -281,25 +281,26 @@ public static void ModifyPixel(ImageFrame img, int x, int y, byt where TPixel : struct, IPixel { TPixel pixel = img[x, y]; - var rgbaPixel = default(Rgba32); - pixel.ToRgba32(ref rgbaPixel); + Rgba64 rgbaPixel = default; + pixel.ToRgba64(ref rgbaPixel); + ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); if (rgbaPixel.R + perChannelChange <= 255) { - rgbaPixel.R += perChannelChange; + rgbaPixel.R += change; } else { - rgbaPixel.R -= perChannelChange; + rgbaPixel.R -= change; } if (rgbaPixel.G + perChannelChange <= 255) { - rgbaPixel.G += perChannelChange; + rgbaPixel.G += change; } else { - rgbaPixel.G -= perChannelChange; + rgbaPixel.G -= change; } if (rgbaPixel.B + perChannelChange <= 255) @@ -320,7 +321,7 @@ public static void ModifyPixel(ImageFrame img, int x, int y, byt rgbaPixel.A -= perChannelChange; } - pixel.PackFromRgba32(rgbaPixel); + pixel.PackFromRgba64(rgbaPixel); img[x, y] = pixel; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index a8f7acb406..a051e577db 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -56,6 +56,8 @@ public enum PixelTypes Bgra32 = 1 << 20, + Rgb48 = 1 << 21, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 1dfb3ba469..f0daa0abb4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Drawing; using System.Drawing.Imaging; using SixLabors.ImageSharp.Advanced; @@ -10,26 +11,35 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { + /// + /// Provides methods to convert to/from System.Drawing bitmaps. + /// public static class SystemDrawingBridge { - internal static unsafe Image FromFromArgb32SystemDrawingBitmap(System.Drawing.Bitmap bmp) + /// + /// Returns an image from the given System.Drawing bitmap. + /// + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) where TPixel : struct, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + var fullRect = new Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format32bppArgb) { - throw new ArgumentException($"FromFromArgb32SystemDrawingBitmap(): pixel format should be Argb32!", nameof(bmp)); + throw new ArgumentException($"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", nameof(bmp)); } BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); byte* sourcePtrBase = (byte*)data.Scan0; long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Argb32); + long destRowByteCount = w * sizeof(Bgra32); var image = new Image(w, h); @@ -41,7 +51,7 @@ internal static unsafe Image FromFromArgb32SystemDrawingBitmap(S { Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + (data.Stride * y); Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); PixelOperations.Instance.PackFromBgra32(workBuffer.GetSpan(), row, row.Length); @@ -53,19 +63,22 @@ internal static unsafe Image FromFromArgb32SystemDrawingBitmap(S } /// - /// TODO: Doesn not work yet! + /// Returns an image from the given System.Drawing bitmap. /// - internal static unsafe Image FromFromRgb24SystemDrawingBitmap(System.Drawing.Bitmap bmp) + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) where TPixel : struct, IPixel { int w = bmp.Width; int h = bmp.Height; - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + var fullRect = new Rectangle(0, 0, w, h); if (bmp.PixelFormat != PixelFormat.Format24bppRgb) { - throw new ArgumentException($"FromFromArgb32SystemDrawingBitmap(): pixel format should be Rgb24!", nameof(bmp)); + throw new ArgumentException($"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", nameof(bmp)); } BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); @@ -84,12 +97,10 @@ internal static unsafe Image FromFromRgb24SystemDrawingBitmap(Sy { Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + (data.Stride * y); Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); PixelOperations.Instance.PackFromBgr24(workBuffer.GetSpan(), row, row.Length); - - // FromRgb24(workBuffer.GetSpan(), row); } } } @@ -97,14 +108,14 @@ internal static unsafe Image FromFromRgb24SystemDrawingBitmap(Sy return image; } - internal static unsafe System.Drawing.Bitmap ToSystemDrawingBitmap(Image image) + internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) where TPixel : struct, IPixel { int w = image.Width; int h = image.Height; - var resultBitmap = new System.Drawing.Bitmap(w, h, PixelFormat.Format32bppArgb); - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); + var fullRect = new Rectangle(0, 0, w, h); BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); byte* destPtrBase = (byte*)data.Scan0; @@ -115,12 +126,11 @@ internal static unsafe System.Drawing.Bitmap ToSystemDrawingBitmap(Image { fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { - for (int y = 0; y < h; y++) { Span row = image.Frames.RootFrame.GetPixelRowSpan(y); PixelOperations.Instance.ToBgra32(row, workBuffer.GetSpan(), row.Length); - byte* destPtr = destPtrBase + data.Stride * y; + byte* destPtr = destPtrBase + (data.Stride * y); Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index b1e53cb6af..427a565424 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -20,7 +20,7 @@ public Image Decode(Configuration configuration, Stream stream) { if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) { - return SystemDrawingBridge.FromFromArgb32SystemDrawingBitmap(sourceBitmap); + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); } using (var convertedBitmap = new System.Drawing.Bitmap( @@ -37,7 +37,8 @@ public Image Decode(Configuration configuration, Stream stream) g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); } - return SystemDrawingBridge.FromFromArgb32SystemDrawingBitmap(convertedBitmap); + + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index ca6f32f5bb..9123336955 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -23,7 +23,7 @@ public SystemDrawingReferenceEncoder(ImageFormat imageFormat) public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.ToSystemDrawingBitmap(image)) + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { sdBitmap.Save(stream, this.imageFormat); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 6bebf3887b..566c22342c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -17,7 +17,7 @@ public static partial class TestEnvironment private static Lazy configuration = new Lazy(CreateDefaultConfiguration); internal static Configuration Configuration => configuration.Value; - + internal static IImageDecoder GetReferenceDecoder(string filePath) { IImageFormat format = GetImageFormat(filePath); @@ -33,7 +33,7 @@ internal static IImageEncoder GetReferenceEncoder(string filePath) internal static IImageFormat GetImageFormat(string filePath) { string extension = Path.GetExtension(filePath); - + IImageFormat format = Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); return format; } @@ -60,11 +60,14 @@ private static Configuration CreateDefaultConfiguration() if (!IsLinux) { + // TODO: System.Drawing on Windows can decode 48bit and 64bit pngs but + // it doesn't preserve the accuracy we require for comparison. + // This makes CompareToOriginal method non-useful. configuration.ConfigureCodecs( ImageFormats.Png, SystemDrawingReferenceDecoder.Instance, SystemDrawingReferenceEncoder.Png, - new PngImageFormatDetector()); + new PngImageFormatDetector()); configuration.ConfigureCodecs( ImageFormats.Bmp, diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index b07a383b79..9a41e66025 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -27,7 +27,7 @@ public static partial class TestEnvironment () => { bool isCi; - return Boolean.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; + return bool.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; }); private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index e135c5d311..016ae7ad29 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -38,15 +38,15 @@ public static void MakeOpaque(this IImageProcessingContext ctx) { Span pixelSpan = frame.GetPixelSpan(); - PixelOperations.Instance.ToVector4(pixelSpan, tempSpan, pixelSpan.Length); + PixelOperations.Instance.ToScaledVector4(pixelSpan, tempSpan, pixelSpan.Length); for (int i = 0; i < tempSpan.Length; i++) { ref Vector4 v = ref tempSpan[i]; - v.W = 1.0f; + v.W = 1F; } - PixelOperations.Instance.PackFromVector4(tempSpan, pixelSpan, pixelSpan.Length); + PixelOperations.Instance.PackFromScaledVector4(tempSpan, pixelSpan, pixelSpan.Length); } } }); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 48c1b391ad..b9fa70f221 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -68,16 +68,14 @@ public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance { using (Image clone = image.Clone()) { - byte perChannelChange = 2; + byte perChannelChange = 20; ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); var comparer = ImageComparer.Tolerant(); ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( - () => - { - comparer.VerifySimilarity(image, clone); - }); + () => comparer.VerifySimilarity(image, clone)); + PixelDifference diff = ex.Reports.Single().Differences.Single(); Assert.Equal(new Point(3, 1), diff.Position); } @@ -85,7 +83,7 @@ public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance } [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) where TPixel : struct, IPixel { @@ -93,11 +91,11 @@ public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvide { using (Image clone = image.Clone()) { - ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 10); - ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 10); - ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 10); + ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1); - var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 42); + var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3); comparer.VerifySimilarity(image, clone); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs index ee398c87b7..3ad595b7e4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs @@ -1,3 +1,4 @@ +using System.IO; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -20,12 +21,12 @@ public ReferenceCodecTests(ITestOutputHelper output) [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void ToSystemDrawingBitmap(TestImageProvider provider) + public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.ToSystemDrawingBitmap(image)) + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { string fileName = provider.Utility.GetTestOutputFileName("png"); sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); @@ -35,14 +36,14 @@ public void ToSystemDrawingBitmap(TestImageProvider provider) [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void FromFromArgb32SystemDrawingBitmap(TestImageProvider dummyProvider) + public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) where TPixel : struct, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); using (var sdBitmap = new System.Drawing.Bitmap(path)) { - using (Image image = SystemDrawingBridge.FromFromArgb32SystemDrawingBitmap(sdBitmap)) + using (Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) { image.DebugSave(dummyProvider); } @@ -59,24 +60,27 @@ private static string SavePng(TestImageProvider provider, PngCol sourceImage.Mutate(c => c.MakeOpaque()); } - var encoder = new PngEncoder() { PngColorType = pngColorType }; + var encoder = new PngEncoder() { ColorType = pngColorType }; return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); } } [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void FromFromArgb32SystemDrawingBitmap2(TestImageProvider provider) + public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) where TPixel : struct, IPixel { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } string path = SavePng(provider, PngColorType.RgbWithAlpha); using (var sdBitmap = new System.Drawing.Bitmap(path)) { using (Image original = provider.GetImage()) - using (Image resaved = SystemDrawingBridge.FromFromArgb32SystemDrawingBitmap(sdBitmap)) + using (Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) { ImageComparer comparer = ImageComparer.Exact; comparer.VerifySimilarity(original, resaved); @@ -85,20 +89,18 @@ public void FromFromArgb32SystemDrawingBitmap2(TestImageProvider } [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void FromFromRgb24SystemDrawingBitmap2(TestImageProvider provider) + [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] + public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) where TPixel : struct, IPixel { string path = SavePng(provider, PngColorType.Rgb); using (Image original = provider.GetImage()) { - original.Mutate(c => c.MakeOpaque()); using (var sdBitmap = new System.Drawing.Bitmap(path)) { - using (Image resaved = SystemDrawingBridge.FromFromRgb24SystemDrawingBitmap(sdBitmap)) + using (Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap)) { - resaved.Mutate(c => c.MakeOpaque()); ImageComparer comparer = ImageComparer.Exact; comparer.VerifySimilarity(original, resaved); } @@ -112,7 +114,7 @@ public void OpenWithReferenceDecoder(TestImageProvider dummyProv where TPixel : struct, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using (Image image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) + using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) { image.DebugSave(dummyProvider); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 4a2c63bebf..f6d3bdb7b9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -103,7 +103,7 @@ public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] - public void GetReferenceDecoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedDecoderType) + public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { if (TestEnvironment.IsLinux) return; @@ -129,7 +129,7 @@ public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Ty [InlineData("lol/Rofl.bmp", typeof(BmpDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] - public void GetReferenceDecoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedDecoderType) + public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { if (!TestEnvironment.IsLinux) return; diff --git a/tests/Images/External b/tests/Images/External index 0e6407be70..6fcee2ccd5 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 0e6407be7081341526f815a4d70e7912db276a98 +Subproject commit 6fcee2ccd5e8bac98a0290b467ad86bb02d00b6c diff --git a/tests/Images/Input/Png/gray-16-tRNS-interlaced.png b/tests/Images/Input/Png/gray-16-tRNS-interlaced.png new file mode 100644 index 0000000000..4b7537e305 Binary files /dev/null and b/tests/Images/Input/Png/gray-16-tRNS-interlaced.png differ diff --git a/tests/Images/Input/Png/gray-16.png b/tests/Images/Input/Png/gray-16.png new file mode 100644 index 0000000000..4826d61eb7 Binary files /dev/null and b/tests/Images/Input/Png/gray-16.png differ diff --git a/tests/Images/Input/Png/gray-alpha-16.png b/tests/Images/Input/Png/gray-alpha-16.png new file mode 100644 index 0000000000..689879737f Binary files /dev/null and b/tests/Images/Input/Png/gray-alpha-16.png differ diff --git a/tests/Images/Input/Png/gray-alpha-8.png b/tests/Images/Input/Png/gray-alpha-8.png new file mode 100644 index 0000000000..eb0a924998 Binary files /dev/null and b/tests/Images/Input/Png/gray-alpha-8.png differ diff --git a/tests/Images/Input/Png/rgb-16-alpha.png b/tests/Images/Input/Png/rgb-16-alpha.png new file mode 100644 index 0000000000..59262397eb Binary files /dev/null and b/tests/Images/Input/Png/rgb-16-alpha.png differ diff --git a/tests/Images/Input/Png/rgb-16-tRNS.png b/tests/Images/Input/Png/rgb-16-tRNS.png new file mode 100644 index 0000000000..64a9cdf2f7 Binary files /dev/null and b/tests/Images/Input/Png/rgb-16-tRNS.png differ diff --git a/tests/Images/Input/Png/rgb-8-tRNS.png b/tests/Images/Input/Png/rgb-8-tRNS.png new file mode 100644 index 0000000000..08ebbae2c8 Binary files /dev/null and b/tests/Images/Input/Png/rgb-8-tRNS.png differ