diff --git a/appveyor.yml b/appveyor.yml index 3e6b79bfc1..5d84e6cea3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.0.{build} -image: Visual Studio 2017 +image: Previous Visual Studio 2017 # prevent the double build when a branch has an active PR skip_branch_with_pr: true diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs index 9f1158154c..8909ca453e 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text } // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it. - Buffer2D fullBuffer = this.MemoryAllocator.Allocate2D(size.Width + 1, size.Height + 1, true); + Buffer2D fullBuffer = this.MemoryAllocator.Allocate2D(size.Width + 1, size.Height + 1, AllocationOptions.Clean); using (IBuffer bufferBacking = this.MemoryAllocator.Allocate(path.MaxIntersections)) using (IBuffer rowIntersectionBuffer = this.MemoryAllocator.Allocate(size.Width)) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs new file mode 100644 index 0000000000..c8b25bf564 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; + +namespace SixLabors.ImageSharp.Common.Helpers +{ + /// + /// Contains methods for converting values between unit scales. + /// + internal static class UnitConverter + { + /// + /// The number of centimeters in a meter. + /// 1 cm is equal to exactly 0.01 meters. + /// + private const double CmsInMeter = 1 / 0.01D; + + /// + /// The number of centimeters in an inch. + /// 1 inch is equal to exactly 2.54 centimeters. + /// + private const double CmsInInch = 2.54D; + + /// + /// The number of inches in a meter. + /// 1 inch is equal to exactly 0.0254 meters. + /// + private const double InchesInMeter = 1 / 0.0254D; + + /// + /// Scales the value from centimeters to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToMeter(double x) => x * CmsInMeter; + + /// + /// Scales the value from meters to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToCm(double x) => x / CmsInMeter; + + /// + /// Scales the value from meters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToInch(double x) => x / InchesInMeter; + + /// + /// Scales the value from inches to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToMeter(double x) => x * InchesInMeter; + + /// + /// Scales the value from centimeters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToInch(double x) => x / CmsInInch; + + /// + /// Scales the value from inches to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToCm(double x) => x * CmsInInch; + + /// + /// Converts an to a . + /// + /// The EXIF profile containing the value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) + { + return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution) + ? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3 + : default; + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 20175613ec..612850e5fd 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -59,6 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private Stream stream; + /// + /// The metadata + /// + private ImageMetaData metaData; + /// /// The file header containing general information. /// TODO: Why is this not used? We advance the stream but do not use the values parsed. @@ -103,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height); + var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -157,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public IImageInfo Identify(Stream stream) { this.ReadImageHeaders(stream, out _, out _); - return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, new ImageMetaData()); + return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metaData); } /// @@ -220,7 +226,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp TPixel color = default; var rgba = new Rgba32(0, 0, 0, 255); - using (Buffer2D buffer = this.memoryAllocator.AllocateClean2D(width, height)) + using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) { this.UncompressRle8(width, buffer.GetSpan()); @@ -348,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp padding = 4 - padding; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocateCleanManagedByteBuffer(arrayWidth + padding)) + using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean)) { TPixel color = default; var rgba = new Rgba32(0, 0, 0, 255); @@ -518,6 +524,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } + // Resolution is stored in PPM. + var meta = new ImageMetaData(); + meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; + if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) + { + meta.HorizontalResolution = this.infoHeader.XPelsPerMeter; + meta.VerticalResolution = this.infoHeader.YPelsPerMeter; + } + else + { + // Convert default metadata values to PPM. + meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultHorizontalResolution)); + meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultVerticalResolution)); + } + + this.metaData = meta; + // skip the remaining header because we can't read those parts this.stream.Skip(skipAmount); } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index a4e61f910b..80fc6330a7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -3,6 +3,8 @@ using System; using System.IO; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -50,6 +52,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); + // Set Resolution. + ImageMetaData meta = image.MetaData; + int hResolution = 0; + int vResolution = 0; + + if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio) + { + if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0) + { + switch (meta.ResolutionUnits) + { + case PixelResolutionUnit.PixelsPerInch: + + hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); + break; + + case PixelResolutionUnit.PixelsPerCentimeter: + + hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); + break; + + case PixelResolutionUnit.PixelsPerMeter: + hResolution = (int)Math.Round(meta.HorizontalResolution); + vResolution = (int)Math.Round(meta.VerticalResolution); + + break; + } + } + } + var infoHeader = new BmpInfoHeader( headerSize: BmpInfoHeader.Size, height: image.Height, @@ -58,7 +92,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp planes: 1, imageSize: image.Height * bytesPerLine, clrUsed: 0, - clrImportant: 0); + clrImportant: 0, + xPelsPerMeter: hResolution, + yPelsPerMeter: vResolution); var fileHeader = new BmpFileHeader( type: 19778, // BM diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index fc73f55a1e..591f787ea9 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -321,11 +321,11 @@ namespace SixLabors.ImageSharp.Formats.Gif if (imageDescriptor.LocalColorTableFlag) { int length = imageDescriptor.LocalColorTableSize * 3; - localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, true); + localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); this.stream.Read(localColorTable.Array, 0, length); } - indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true); + indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean); this.ReadFrameIndices(imageDescriptor, indices.GetSpan()); ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); @@ -450,8 +450,8 @@ namespace SixLabors.ImageSharp.Formats.Gif { int index = Unsafe.Add(ref indicesRef, i); - if (this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) + if (!this.graphicsControlExtension.TransparencyFlag + || this.graphicsControlExtension.TransparencyIndex != index) { ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); rgba.Rgb = colorTable[index]; @@ -516,19 +516,47 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The stream containing image data. private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) { - this.metaData = new ImageMetaData(); - this.stream = stream; // Skip the identifier this.stream.Skip(6); this.ReadLogicalScreenDescriptor(); + var meta = new ImageMetaData(); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + if (this.logicalScreenDescriptor.PixelAspectRatio > 0) + { + meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; + float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; + + if (ratio > 1) + { + meta.HorizontalResolution = ratio; + meta.VerticalResolution = 1; + } + else + { + meta.VerticalResolution = 1 / ratio; + meta.HorizontalResolution = 1; + } + } + + this.metaData = meta; + if (this.logicalScreenDescriptor.GlobalColorTableFlag) { int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, true); + this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); // Read the global color table data from the stream stream.Read(this.globalColorTable.Array, 0, globalColorTableLength); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index e4737f3bc5..ea507c7811 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -226,11 +226,41 @@ namespace SixLabors.ImageSharp.Formats.Gif { byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1); + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + ImageMetaData meta = image.MetaData; + byte ratio = 0; + + if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio) + { + double hr = meta.HorizontalResolution; + double vr = meta.VerticalResolution; + if (hr != vr) + { + if (hr > vr) + { + ratio = (byte)((hr * 64) - 15); + } + else + { + ratio = (byte)(((1 / vr) * 64) - 15); + } + } + } + var descriptor = new GifLogicalScreenDescriptor( width: (ushort)image.Width, height: (ushort)image.Height, packed: packedValue, - backgroundColorIndex: unchecked((byte)transparencyIndex)); + backgroundColorIndex: unchecked((byte)transparencyIndex), + ratio); descriptor.WriteTo(this.buffer); diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 35eb43a8a8..2884abfe87 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream = stream; - this.prefix = memoryAllocator.Allocate(MaxStackSize, true); - this.suffix = memoryAllocator.Allocate(MaxStackSize, true); - this.pixelStack = memoryAllocator.Allocate(MaxStackSize + 1, true); + this.prefix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.suffix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.pixelStack = memoryAllocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 347609a549..5a588a671c 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -168,8 +168,8 @@ namespace SixLabors.ImageSharp.Formats.Gif public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) { this.initialCodeSize = Math.Max(2, colorDepth); - this.hashTable = memoryAllocator.Allocate(HashSize, true); - this.codeTable = memoryAllocator.Allocate(HashSize, true); + this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 591af63442..f153ce062a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.MetaData; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -28,10 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Guard.MustBeGreaterThan(xDensity, 0, nameof(xDensity)); Guard.MustBeGreaterThan(yDensity, 0, nameof(yDensity)); + Guard.MustBeBetweenOrEqualTo(densityUnits, 0, 2, nameof(densityUnits)); this.MajorVersion = majorVersion; this.MinorVersion = minorVersion; - this.DensityUnits = densityUnits; + this.DensityUnits = (PixelResolutionUnit)densityUnits; this.XDensity = xDensity; this.YDensity = yDensity; } @@ -52,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 01 : Pixels per inch (2.54 cm) /// 02 : Pixels per centimeter /// - public byte DensityUnits { get; } + public PixelResolutionUnit DensityUnits { get; } /// /// Gets the horizontal pixel density. Must not be zero. diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs index 75cea5551f..72213eb38c 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = memoryAllocator.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); + this.SpectralBlocks = memoryAllocator.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 1310d90d26..ada33f2b84 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; @@ -210,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution); + this.WriteApplicationHeader(image.MetaData); this.WriteProfiles(image); @@ -425,9 +427,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the application header containing the JFIF identifier plus extra data. /// - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. - private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + /// The image meta data. + private void WriteApplicationHeader(ImageMetaData meta) { // Write the start of image marker. Markers are always prefixed with with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; @@ -445,13 +446,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[11] = 0x01; // versionhi this.buffer[12] = 0x01; // versionlo - this.buffer[13] = 0x01; // xyunits as dpi // Resolution. Big Endian - this.buffer[14] = (byte)(horizontalResolution >> 8); - this.buffer[15] = (byte)horizontalResolution; - this.buffer[16] = (byte)(verticalResolution >> 8); - this.buffer[17] = (byte)verticalResolution; + Span hResolution = this.buffer.AsSpan(14, 2); + Span vResolution = this.buffer.AsSpan(16, 2); + + if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) + { + // Scale down to PPI + this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); + } + else + { + // We can simply pass the value. + this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); + } // No thumbnail this.buffer[18] = 0x00; // Thumbnail width diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs index 7aeee43c2e..0e42d074c2 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { this.stream = stream; this.length = (int)stream.Length; - this.managedBuffer = memoryAllocator.AllocateCleanManagedByteBuffer(ChunkLength); + this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean); this.bufferChunk = this.managedBuffer.Array; } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs index 6cb0d6dfe5..0fc85c6e4c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// The memory allocator used to allocate memory for image processing operations. public FastACTables(MemoryAllocator memoryAllocator) { - this.tables = memoryAllocator.AllocateClean2D(512, 4); + this.tables = memoryAllocator.Allocate2D(512, 4, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index 7501b0d83c..7d4eb66e82 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = this.memoryAllocator.AllocateClean2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1); + this.SpectralBlocks = this.memoryAllocator.Allocate2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1, AllocationOptions.Clean); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index a360d54771..69b5e93b6e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; @@ -412,6 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { this.MetaData.HorizontalResolution = this.jFif.XDensity; this.MetaData.VerticalResolution = this.jFif.YDensity; + this.MetaData.ResolutionUnits = this.jFif.DensityUnits; } else if (this.isExif) { @@ -427,6 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { this.MetaData.HorizontalResolution = horizontalValue; this.MetaData.VerticalResolution = verticalValue; + this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); } } @@ -707,7 +709,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(int remaining) { - using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256)) + using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) { ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); for (int i = 2; i < remaining;) @@ -715,7 +717,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(17)) + using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) { ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); int codeLengthSum = 0; @@ -725,7 +727,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); } - using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256)) + using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 04d4f057ce..83c195eecc 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.MetaData; @@ -233,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ValidateHeader(); break; case PngChunkType.Physical: - this.ReadPhysicalChunk(metadata, chunk.Data.Array); + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: if (image == null) @@ -307,7 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ValidateHeader(); break; case PngChunkType.Physical: - this.ReadPhysicalChunk(metadata, chunk.Data.Array); + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: this.SkipChunkDataAndCrc(chunk); @@ -369,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.Png return false; } - buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits); + buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); byte[] result = buffer.Array; int mask = 0xFF >> (8 - bits); int resultOffset = 0; @@ -396,9 +397,26 @@ namespace SixLabors.ImageSharp.Formats.Png /// The data containing physical data. private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan data) { - // 39.3700787 = inches in a meter. - metadata.HorizontalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)) / 39.3700787d; - metadata.VerticalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)) / 39.3700787d; + // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains: + // Pixels per unit, X axis: 4 bytes (unsigned integer) + // Pixels per unit, Y axis: 4 bytes (unsigned integer) + // Unit specifier: 1 byte + // + // The following values are legal for the unit specifier: + // 0: unit is unknown + // 1: unit is the meter + // + // When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. + int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)); + int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)); + byte unit = data[8]; + + metadata.ResolutionUnits = unit == byte.MinValue + ? PixelResolutionUnit.AspectRatio + : PixelResolutionUnit.PixelsPerMeter; + + metadata.HorizontalResolution = hResolution; + metadata.VerticalResolution = vResolution; } /// @@ -419,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = this.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); - this.scanline = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); } /// @@ -1427,7 +1445,7 @@ namespace SixLabors.ImageSharp.Formats.Png private IManagedByteBuffer ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() - IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(length); + IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); this.currentStream.Read(buffer.Array, 0, length); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 69f04979cf..e696e1f686 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -6,8 +6,10 @@ using System.Buffers.Binary; using System.IO; using System.Linq; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.Memory; @@ -598,19 +600,52 @@ namespace SixLabors.ImageSharp.Formats.Png private void WritePhysicalChunk(Stream stream, Image image) where TPixel : struct, IPixel { - if (image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) + // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains: + // Pixels per unit, X axis: 4 bytes (unsigned integer) + // Pixels per unit, Y axis: 4 bytes (unsigned integer) + // Unit specifier: 1 byte + // + // The following values are legal for the unit specifier: + // 0: unit is unknown + // 1: unit is the meter + // + // When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. + ImageMetaData meta = image.MetaData; + Span hResolution = this.chunkDataBuffer.AsSpan(0, 4); + Span vResolution = this.chunkDataBuffer.AsSpan(4, 4); + + switch (meta.ResolutionUnits) { - // 39.3700787 = inches in a meter. - int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D); - int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D); + case PixelResolutionUnit.AspectRatio: - BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), dpmX); - BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), dpmY); + this.chunkDataBuffer[8] = 0; + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); + break; + + case PixelResolutionUnit.PixelsPerInch: + + this.chunkDataBuffer[8] = 1; // Per meter + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution))); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution))); + break; + + case PixelResolutionUnit.PixelsPerCentimeter: - this.chunkDataBuffer[8] = 1; + this.chunkDataBuffer[8] = 1; // Per meter + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution))); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution))); + break; + + default: - this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); + this.chunkDataBuffer[8] = 1; // Per meter + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); + break; } + + this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); } /// @@ -643,9 +678,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerScanline = this.width * this.bytesPerPixel; int resultLength = this.bytesPerScanline + 1; - this.previousScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); - this.rawScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); - this.result = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.rawScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.result = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); switch (this.pngFilterMethod) { @@ -654,29 +689,29 @@ namespace SixLabors.ImageSharp.Formats.Png case PngFilterMethod.Sub: - this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Up: - this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Average: - this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Paeth: - this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); + this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); 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); + this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 93e888ccaa..aa650d3808 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp this.configuration = configuration; this.MemoryAllocator = configuration.MemoryAllocator; - this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height, false); + this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height); this.MetaData = metaData; this.Clear(configuration.ParallelOptions, backgroundColor); } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index b40884f4b1..eca1341015 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -29,7 +29,7 @@ portable True IOperation - 7.3 + Latest diff --git a/src/ImageSharp/Memory/AllocationOptions.cs b/src/ImageSharp/Memory/AllocationOptions.cs new file mode 100644 index 0000000000..5eda00505e --- /dev/null +++ b/src/ImageSharp/Memory/AllocationOptions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.Memory +{ + /// + /// Options for allocating buffers. + /// + public enum AllocationOptions + { + /// + /// Indicates that the buffer should just be allocated. + /// + None, + + /// + /// Indicates that the allocated buffer should be cleaned following allocation. + /// + Clean + } +} diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs index f1c4c49c34..08f28c5b3b 100644 --- a/src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs @@ -89,7 +89,7 @@ namespace SixLabors.Memory } /// - internal override IBuffer Allocate(int length, bool clear) + internal override IBuffer Allocate(int length, AllocationOptions options) { int itemSizeBytes = Unsafe.SizeOf(); int bufferSizeInBytes = length * itemSizeBytes; @@ -98,7 +98,7 @@ namespace SixLabors.Memory byte[] byteArray = pool.Rent(bufferSizeInBytes); var buffer = new Buffer(byteArray, length, pool); - if (clear) + if (options == AllocationOptions.Clean) { buffer.Clear(); } @@ -107,13 +107,13 @@ namespace SixLabors.Memory } /// - internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options) { ArrayPool pool = this.GetArrayPool(length); byte[] byteArray = pool.Rent(length); var buffer = new ManagedByteBuffer(byteArray, length, pool); - if (clear) + if (options == AllocationOptions.Clean) { buffer.Clear(); } diff --git a/src/ImageSharp/Memory/MemoryAllocator.cs b/src/ImageSharp/Memory/MemoryAllocator.cs index 2195444fd6..4a65848fc8 100644 --- a/src/ImageSharp/Memory/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/MemoryAllocator.cs @@ -9,23 +9,22 @@ namespace SixLabors.Memory public abstract class MemoryAllocator { /// - /// Allocates an of size , optionally - /// clearing the buffer before it gets returned. + /// Allocates an of size . /// /// Type of the data stored in the buffer /// Size of the buffer to allocate - /// True to clear the backing memory of the buffer + /// The allocation options. /// A buffer of values of type . - internal abstract IBuffer Allocate(int length, bool clear) + internal abstract IBuffer Allocate(int length, AllocationOptions options = AllocationOptions.None) where T : struct; /// - /// Allocates an + /// Allocates an . /// /// The requested buffer length - /// A value indicating whether to clean the buffer + /// The allocation options. /// The - internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear); + internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); /// /// Releases all retained resources not being in use. diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index ab4fc93b7d..2f296636d4 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -7,56 +7,17 @@ namespace SixLabors.Memory /// internal static class MemoryAllocatorExtensions { - /// - /// Allocates a of size . - /// Note: Depending on the implementation, the buffer may not cleared before - /// returning, so it may contain data from an earlier use. - /// - /// Type of the data stored in the buffer - /// The - /// Size of the buffer to allocate - /// A buffer of values of type . - public static IBuffer Allocate(this MemoryAllocator memoryAllocator, int length) + public static Buffer2D Allocate2D(this MemoryAllocator memoryAllocator, int width, int height, AllocationOptions options = AllocationOptions.None) where T : struct { - return memoryAllocator.Allocate(length, false); - } - - public static IBuffer AllocateClean(this MemoryAllocator memoryAllocator, int length) - where T : struct - { - return memoryAllocator.Allocate(length, true); - } - - public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryAllocator memoryAllocator, int length) - { - return memoryAllocator.AllocateManagedByteBuffer(length, false); - } - - public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryAllocator memoryAllocator, int length) - { - return memoryAllocator.AllocateManagedByteBuffer(length, true); - } - - public static Buffer2D Allocate2D(this MemoryAllocator memoryAllocator, int width, int height, bool clear) - where T : struct - { - IBuffer buffer = memoryAllocator.Allocate(width * height, clear); + IBuffer buffer = memoryAllocator.Allocate(width * height, options); return new Buffer2D(buffer, width, height); } - public static Buffer2D Allocate2D(this MemoryAllocator memoryAllocator, Size size) - where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, false); - - public static Buffer2D Allocate2D(this MemoryAllocator memoryAllocator, int width, int height) - where T : struct => - Allocate2D(memoryAllocator, width, height, false); - - public static Buffer2D AllocateClean2D(this MemoryAllocator memoryAllocator, int width, int height) + public static Buffer2D Allocate2D(this MemoryAllocator memoryAllocator, Size size, AllocationOptions options = AllocationOptions.None) where T : struct => - Allocate2D(memoryAllocator, width, height, true); + Allocate2D(memoryAllocator, size.Width, size.Height, options); /// /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) diff --git a/src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs index df69cec6eb..8cb0573661 100644 --- a/src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs @@ -6,12 +6,12 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - internal override IBuffer Allocate(int length, bool clear) + internal override IBuffer Allocate(int length, AllocationOptions options) { return new BasicArrayBuffer(new T[length]); } - internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options) { return new BasicByteBuffer(new byte[length]); } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index af3cc5f5fd..40880bd085 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -14,13 +14,13 @@ namespace SixLabors.ImageSharp.MetaData { /// /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 dots per inch. + /// The default value is 96 . /// public const double DefaultHorizontalResolution = 96; /// /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 dots per inch. + /// The default value is 96 . /// public const double DefaultVerticalResolution = 96; @@ -47,6 +47,7 @@ namespace SixLabors.ImageSharp.MetaData { this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; + this.ResolutionUnits = other.ResolutionUnits; this.RepeatCount = other.RepeatCount; foreach (ImageProperty property in other.Properties) @@ -99,6 +100,15 @@ namespace SixLabors.ImageSharp.MetaData } } + /// + /// Gets or sets unit of measure used when reporting resolution. + /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// 01 : Pixels per inch (2.54 cm) + /// 02 : Pixels per centimeter + /// 03 : Pixels per meter + /// + public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; + /// /// Gets or sets the Exif profile. /// diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs new file mode 100644 index 0000000000..ce848004f7 --- /dev/null +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.MetaData +{ + /// + /// Provides enumeration of available pixel density units. + /// + public enum PixelResolutionUnit : byte + { + /// + /// No units; width:height pixel aspect ratio. + /// + AspectRatio = 0, + + /// + /// Pixels per inch (2.54 cm). + /// + PixelsPerInch = 1, + + /// + /// Pixels per centimeter. + /// + PixelsPerCentimeter = 2, + + /// + /// Pixels per meter (100 cm). + /// + PixelsPerMeter = 3 + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 6828f11dcc..84e528d246 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -278,7 +278,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -395,7 +395,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -434,7 +434,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -473,7 +473,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -512,7 +512,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -551,7 +551,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -668,7 +668,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -707,7 +707,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -746,7 +746,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -785,7 +785,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -824,7 +824,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 07888a756d..cf16f9705e 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { using System; using System.Numerics; - using SixLabors.ImageSharp.Memory; + using SixLabors.Memory; /// @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs new file mode 100644 index 0000000000..8dabfcc05c --- /dev/null +++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Normalization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extension that allow the adjustment of the contrast of an image via its histogram. + /// + public static class HistogramEqualizationExtension + { + /// + /// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) + where TPixel : struct, IPixel + => HistogramEqualization(source, 65536); + + /// + /// Equalizes the histogram of an image to increases the global contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, int luminanceLevels) + where TPixel : struct, IPixel + => source.ApplyProcessor(new HistogramEqualizationProcessor(luminanceLevels)); + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs new file mode 100644 index 0000000000..b834b8968c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies a global histogram equalization to the image. + /// + /// The pixel format. + internal class HistogramEqualizationProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + public HistogramEqualizationProcessor(int luminanceLevels) + { + Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); + + this.LuminanceLevels = luminanceLevels; + } + + /// + /// Gets the number of luminance levels. + /// + public int LuminanceLevels { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + + // Build the histogram of the grayscale levels. + using (IBuffer histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IBuffer cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + for (int i = 0; i < pixels.Length; i++) + { + TPixel sourcePixel = pixels[i]; + int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); + histogram[luminance]++; + } + + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + Span cdf = cdfBuffer.GetSpan(); + int cdfMin = this.CalculateCdf(cdf, histogram); + + // Apply the cdf to each pixel of the image + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + for (int i = 0; i < pixels.Length; i++) + { + TPixel sourcePixel = pixels[i]; + + int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels); + float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin; + + pixels[i].PackFromVector4(new Vector4(luminanceEqualized)); + } + } + } + + /// + /// Calculates the cumulative distribution function. + /// + /// The array holding the cdf. + /// The histogram of the input image. + /// The first none zero value of the cdf. + private int CalculateCdf(Span cdf, Span histogram) + { + // Calculate the cumulative histogram + int histSum = 0; + for (int i = 0; i < histogram.Length; i++) + { + histSum += histogram[i]; + cdf[i] = histSum; + } + + // Get the first none zero value of the cumulative histogram + int cdfMin = 0; + for (int i = 0; i < histogram.Length; i++) + { + if (cdf[i] != 0) + { + cdfMin = cdf[i]; + break; + } + } + + // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop + for (int i = 0; i < histogram.Length; i++) + { + cdf[i] = Math.Max(0, cdf[i] - cdfMin); + } + + return cdfMin; + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + private int GetLuminance(TPixel sourcePixel, int luminanceLevels) + { + // Convert to grayscale using ITU-R Recommendation BT.709 + var vector = sourcePixel.ToVector4(); + int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); + + return luminance; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 977b939a1c..6c2e6173f7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Width = width; this.Height = height; this.Palette = palette; - this.pixels = memoryAllocator.AllocateCleanManagedByteBuffer(width * height); + this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 7c2ff77e36..9a2a84e811 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -139,13 +139,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization try { - this.vwt = memoryAllocator.AllocateClean(TableLength); - this.vmr = memoryAllocator.AllocateClean(TableLength); - this.vmg = memoryAllocator.AllocateClean(TableLength); - this.vmb = memoryAllocator.AllocateClean(TableLength); - this.vma = memoryAllocator.AllocateClean(TableLength); - this.m2 = memoryAllocator.AllocateClean(TableLength); - this.tag = memoryAllocator.AllocateClean(TableLength); + this.vwt = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmr = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmg = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmb = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vma = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.m2 = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tag = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); return base.QuantizeFrame(image); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs index 581a3353ae..3983ea091f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The size of the destination window public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize) { - this.dataBuffer = memoryAllocator.Allocate2D(sourceSize, destinationSize, true); + this.dataBuffer = memoryAllocator.Allocate2D(sourceSize, destinationSize, AllocationOptions.Clean); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index b994af0566..09c3d1545f 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -10,17 +10,25 @@ using Xunit; namespace SixLabors.ImageSharp.Tests { + using SixLabors.ImageSharp.MetaData; using static TestImages.Bmp; public class BmpDecoderTests { - public const PixelTypes CommonNonDefaultPixelTypes = - PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public static readonly string[] AllBmpFiles = - { - Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted - }; + { + Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] @@ -64,5 +72,39 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new BmpDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new BmpDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 8d29536b26..d887d23ade 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -12,11 +14,19 @@ namespace SixLabors.ImageSharp.Tests public class BmpEncoderTests : FileTestBase { public static readonly TheoryData BitsPerPixel = - new TheoryData - { - BmpBitsPerPixel.Pixel24, - BmpBitsPerPixel.Pixel32 - }; + new TheoryData + { + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; public BmpEncoderTests(ITestOutputHelper output) { @@ -25,6 +35,31 @@ namespace SixLabors.ImageSharp.Tests private ITestOutputHelper Output { get; } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new BmpEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetaData meta = output.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) @@ -44,10 +79,10 @@ namespace SixLabors.ImageSharp.Tests { TestBmpEncoderCore(provider, bitsPerPixel); } - + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel - { + { using (Image image = provider.GetImage()) { // there is no alpha in bmp! diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index ceb60ae5c9..6d2a74c03b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -4,7 +4,6 @@ using System.Text; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using Xunit; using System.IO; using SixLabors.ImageSharp.Advanced; @@ -13,7 +12,7 @@ using SixLabors.ImageSharp.Advanced; namespace SixLabors.ImageSharp.Tests.Formats.Gif { using System.Collections.Generic; - + using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; public class GifDecoderTests @@ -21,31 +20,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; public static readonly string[] MultiFrameTestFiles = - { - TestImages.Gif.Giphy, TestImages.Gif.Kumin - }; + { + TestImages.Gif.Giphy, TestImages.Gif.Kumin + }; public static readonly string[] BasicVerificationFiles = - { - TestImages.Gif.Cheers, - TestImages.Gif.Rings, + { + TestImages.Gif.Cheers, + TestImages.Gif.Rings, - // previously DecodeBadApplicationExtensionLength: - TestImages.Gif.Issues.BadAppExtLength, - TestImages.Gif.Issues.BadAppExtLength_2, + // previously DecodeBadApplicationExtensionLength: + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2, - // previously DecodeBadDescriptorDimensionsLength: - TestImages.Gif.Issues.BadDescriptorWidth - }; + // previously DecodeBadDescriptorDimensionsLength: + TestImages.Gif.Issues.BadDescriptorWidth + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Gif.Rings, (int)ImageMetaData.DefaultHorizontalResolution, (int)ImageMetaData.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; private static readonly Dictionary BasicVerificationFrameCount = - new Dictionary - { - [TestImages.Gif.Cheers] = 93, - [TestImages.Gif.Issues.BadDescriptorWidth] = 36, - }; + new Dictionary + { + [TestImages.Gif.Cheers] = 93, + [TestImages.Gif.Issues.BadDescriptorWidth] = 36, + }; - public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 }; + public static readonly string[] BadAppExtFiles = + { + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2 + }; [Theory] [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] @@ -59,6 +70,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + [Theory] [WithFile(TestImages.Gif.Trans, TestPixelTypes)] public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) @@ -88,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } } - + [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { @@ -169,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [InlineData(TestImages.Gif.Trans, 8)] 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); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 2b9c11fb03..e9104ef8d9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -17,6 +17,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.001F); + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Gif.Rings, (int)ImageMetaData.DefaultHorizontalResolution, (int)ImageMetaData.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + [Theory] [WithTestPatternImages(100, 100, TestPixelTypes)] public void EncodeGeneratedPatterns(TestImageProvider provider) @@ -43,6 +51,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new GifEncoder() + { + IgnoreMetadata = false + }; + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetaData meta = output.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index 332899e8df..b2dc3534d1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - +using SixLabors.ImageSharp.MetaData; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.True(isJFif); Assert.Equal(1, marker.MajorVersion); Assert.Equal(1, marker.MinorVersion); - Assert.Equal(1, marker.DensityUnits); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); Assert.Equal(96, marker.XDensity); Assert.Equal(96, marker.YDensity); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs index 10b098d924..f217f0df14 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs @@ -15,30 +15,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.MetaData; public partial class JpegDecoderTests { // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) public static readonly TheoryData MetaDataTestData = - new TheoryData - { - { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - - { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - }; + new TheoryData + { + { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + + { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, + { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; [Theory] [MemberData(nameof(MetaDataTestData))] @@ -76,14 +85,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg iccProfilePresent); } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { IImageInfo imageInfo = useIdentify - ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) - : decoder.Decode(Configuration.Default, stream); + ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) + : decoder.Decode(Configuration.Default, stream); + test(imageInfo); } } @@ -141,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } }); } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -166,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } - + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 911812ebb2..a31ae37b75 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -3,6 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -14,16 +15,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class JpegEncoderTests { public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData - { - { JpegSubsample.Ratio420, 40 }, - { JpegSubsample.Ratio420, 60 }, - { JpegSubsample.Ratio420, 100 }, + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, - { JpegSubsample.Ratio444, 40 }, - { JpegSubsample.Ratio444, 60 }, - { JpegSubsample.Ratio444, 100 }, - }; + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, + { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] @@ -82,10 +91,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg image.Mutate(c => c.MakeOpaque()); var encoder = new JpegEncoder() - { - Subsample = subsample, - Quality = quality - }; + { + Subsample = subsample, + Quality = quality + }; string info = $"{subsample}-Q{quality}"; ImageComparer comparer = GetComparer(quality, subsample); @@ -93,7 +102,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } } - [Theory] [InlineData(false)] @@ -104,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { IgnoreMetadata = ignoreMetaData }; - + using (Image input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) { using (var memStream = new MemoryStream()) @@ -126,7 +134,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } - + [Fact] public void Quality_0_And_1_Are_Identical() { @@ -172,5 +180,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); } } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new JpegEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetaData meta = output.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 6f04ba651d..dc29b19497 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -77,8 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [InlineData((uint)PngChunkType.Gamma)] // gAMA [InlineData((uint)PngChunkType.PaletteAlpha)] // tRNS - [InlineData( - (uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks. + [InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks. //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType) { @@ -112,9 +111,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteChunk(MemoryStream memStream, string chunkName) { - memStream.Write(new byte[] { 0, 0, 0, 1 }, 0, 4); - memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); - memStream.Write(new byte[] { 0, 0, 0, 0, 0 }, 0, 5); + // Needs a minimum length of 9 for pHYs chunk. + memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4); + memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); // 4 bytes chunk header + memStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 9); // 9 bytes of chunk data + memStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Junk Crc } private static void WriteDataChunk(MemoryStream memStream) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 66e4f39fd0..54f3e397c3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -8,6 +8,7 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -18,8 +19,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public partial class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; - - public static readonly string[] CommonTestImages = { @@ -67,6 +66,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.GrayTrns16BitInterlaced }; + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, + { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) @@ -218,5 +225,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetaData meta = image.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 540fc0716c..62de45064a 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -7,6 +7,7 @@ using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -61,6 +62,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 80, 100, 120, 230 }; + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, + { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] @@ -256,5 +265,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Equal(expected, data); } } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new PngEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetaData meta = output.MetaData; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs new file mode 100644 index 0000000000..57e280d938 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Common.Helpers; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class UnitConverterHelperTests + { + [Fact] + public void InchToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.InchToMeter(expected); + actual = UnitConverter.MeterToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void InchToFromCm() + { + const double expected = 96D; + double actual = UnitConverter.InchToCm(expected); + actual = UnitConverter.CmToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void CmToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.CmToMeter(expected); + actual = UnitConverter.MeterToCm(actual); + + Assert.Equal(expected, actual, 15); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs index c8e304b41a..14fbe8225d 100644 --- a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs +++ b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs @@ -126,18 +126,18 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(false)] - [InlineData(true)] - public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean) + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) { using (IBuffer firstAlloc = this.MemoryAllocator.Allocate(42)) { firstAlloc.GetSpan().Fill(666); } - using (IBuffer secondAlloc = this.MemoryAllocator.Allocate(42, clean)) + using (IBuffer secondAlloc = this.MemoryAllocator.Allocate(42, options)) { - int expected = clean ? 0 : 666; + int expected = options == AllocationOptions.Clean ? 0 : 666; Assert.Equal(expected, secondAlloc.GetSpan()[0]); } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 301a76f562..5dd8572dc1 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -34,11 +34,11 @@ namespace SixLabors.ImageSharp.Tests.Memory private class MockMemoryAllocator : MemoryAllocator { - internal override IBuffer Allocate(int length, bool clear) + internal override IBuffer Allocate(int length, AllocationOptions options) { var array = new T[length + 42]; - if (!clear) + if (options == AllocationOptions.None) { Span data = MemoryMarshal.Cast(array.AsSpan()); for (int i = 0; i < data.Length; i++) @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Memory return new BasicArrayBuffer(array, length); } - internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options) { throw new NotImplementedException(); } @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void CreateClean() { - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, true)) + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { Span span = buffer.GetSpan(); for (int j = 0; j < span.Length; j++) diff --git a/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs index a0a68a7058..fd33312880 100644 --- a/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs @@ -124,12 +124,12 @@ namespace SixLabors.ImageSharp.Tests.Memory this.TestCanAllocateCleanBuffer(desiredLength); } - private IBuffer Allocate(int desiredLength, bool clean, bool managedByteBuffer) + private IBuffer Allocate(int desiredLength, AllocationOptions options, bool managedByteBuffer) where T : struct { if (managedByteBuffer) { - if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer buffer)) + if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IBuffer buffer)) { throw new InvalidOperationException("typeof(T) != typeof(byte)"); } @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Tests.Memory return buffer; } - return this.MemoryAllocator.Allocate(desiredLength, clean); + return this.MemoryAllocator.Allocate(desiredLength, options); } private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int i = 0; i < 10; i++) { - using (IBuffer buffer = this.Allocate(desiredLength, true, testManagedByteBuffer)) + using (IBuffer buffer = this.Allocate(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) { Assert.True(buffer.GetSpan().SequenceEqual(expected)); } @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) where T : struct { - using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + using (IBuffer buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) { ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) where T : struct { - using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + using (IBuffer buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) { T[] expectedVals = new T[buffer.Length()]; @@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { var dummy = default(T); - using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + using (IBuffer buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) { Assert.ThrowsAny( () => diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs new file mode 100644 index 0000000000..1595ed32cc --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Normalization +{ + public class HistogramEqualizationTests + { + [Theory] + [InlineData(256)] + [InlineData(65536)] + public void HistogramEqualizationTest(int luminanceLevels) + { + // Arrange + byte[] pixels = new byte[] + { + 52, 55, 61, 59, 70, 61, 76, 61, + 62, 59, 55, 104, 94, 85, 59, 71, + 63, 65, 66, 113, 144, 104, 63, 72, + 64, 70, 70, 126, 154, 109, 71, 69, + 67, 73, 68, 106, 122, 88, 68, 68, + 68, 79, 60, 79, 77, 66, 58, 75, + 69, 85, 64, 58, 55, 61, 65, 83, + 70, 87, 69, 68, 65, 73, 78, 90 + }; + + var image = new Image(8, 8); + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + byte luminance = pixels[y * 8 + x]; + image[x, y] = new Rgba32(luminance, luminance, luminance); + } + } + + byte[] expected = new byte[] + { + 0, 12, 53, 32, 146, 53, 174, 53, + 57, 32, 12, 227, 219, 202, 32, 154, + 65, 85, 93, 239, 251, 227, 65, 158, + 73, 146, 146, 247, 255, 235, 154, 130, + 97, 166, 117, 231, 243, 210, 117, 117, + 117, 190, 36, 190, 178, 93, 20, 170, + 130, 202, 73, 20, 12, 53, 85, 194, + 146, 206, 130, 117, 85, 166, 182, 215 + }; + + // Act + image.Mutate(x => x.HistogramEqualization(luminanceLevels)); + + // Assert + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + Rgba32 actual = image[x, y]; + Assert.Equal(expected[y * 8 + x], actual.R); + Assert.Equal(expected[y * 8 + x], actual.G); + Assert.Equal(expected[y * 8 + x], actual.B); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b0bdad8e5c..142b923ed1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -65,6 +65,9 @@ namespace SixLabors.ImageSharp.Tests public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png"; public const string Banner8Index = "Png/banner8-index.png"; + public const string Ratio1x4 = "Png/ratio-1x4.png"; + public const string Ratio4x1 = "Png/ratio-4x1.png"; + public static class Bad { // Odd chunk lengths @@ -79,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, FilterVar, VimImage1, VimImage2, VersioningImage1, - VersioningImage2 + VersioningImage2, Ratio4x1, Ratio1x4 }; } @@ -124,13 +127,14 @@ namespace SixLabors.ImageSharp.Tests public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; public const string Testorig420 = "Jpg/baseline/testorig.jpg"; public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; + public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; public static readonly string[] All = - { - Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, - }; + { + Cmyk, Ycck, Exif, Floorplan, + Calliphora, Turtle, GammaDalaiLamaGray, + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1 + }; } public static class Issues @@ -176,15 +180,17 @@ namespace SixLabors.ImageSharp.Tests public const string Cheers = "Gif/cheers.gif"; public const string Trans = "Gif/trans.gif"; public const string Kumin = "Gif/kumin.gif"; + public const string Ratio4x1 = "Gif/base_4x1.gif"; + public const string Ratio1x4 = "Gif/base_1x4.gif"; - public class Issues + public static class Issues { public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; } - public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin }; + public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Ratio4x1, Ratio1x4 }; } } } diff --git a/tests/Images/Input/Gif/base_1x4.gif b/tests/Images/Input/Gif/base_1x4.gif new file mode 100644 index 0000000000..b4b89b525a --- /dev/null +++ b/tests/Images/Input/Gif/base_1x4.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56e2409223f140145db2ba405f5562451dc0fa9d4274830fb02bd78d42552162 +size 1620 diff --git a/tests/Images/Input/Gif/base_4x1.gif b/tests/Images/Input/Gif/base_4x1.gif new file mode 100644 index 0000000000..31d2c07e8c --- /dev/null +++ b/tests/Images/Input/Gif/base_4x1.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24fd7e9dd3c6516ddab7336a30efc5901754b6d43a9d989c6fbb3e06d8944c80 +size 1620 diff --git a/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg new file mode 100644 index 0000000000..e5d987e8c4 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e9410c16bc61f08bbc18ae0e0b13181c9c4f57c66e82a7c6e593a57f40756d7 +size 34674 diff --git a/tests/Images/Input/Png/ratio-1x4.png b/tests/Images/Input/Png/ratio-1x4.png new file mode 100644 index 0000000000..37bbb27e62 --- /dev/null +++ b/tests/Images/Input/Png/ratio-1x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:438018f19c85e582cb586ac7cca2220008ecb7fc70ce50e8f2a76b494c128a20 +size 404 diff --git a/tests/Images/Input/Png/ratio-4x1.png b/tests/Images/Input/Png/ratio-4x1.png new file mode 100644 index 0000000000..b494a47d18 --- /dev/null +++ b/tests/Images/Input/Png/ratio-4x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a068eaf1f7040490e08eda3259befb6689849dd0ff8bb4cc03c705d117cb2b9f +size 344