From 2278789736ae646eb1d8195efbbf1d66b905733f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 24 Jun 2018 01:46:23 +1000 Subject: [PATCH 01/14] Create new DensityUnits enum. --- src/ImageSharp/Formats/DensityUnits.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/ImageSharp/Formats/DensityUnits.cs diff --git a/src/ImageSharp/Formats/DensityUnits.cs b/src/ImageSharp/Formats/DensityUnits.cs new file mode 100644 index 000000000..ce30bd06c --- /dev/null +++ b/src/ImageSharp/Formats/DensityUnits.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Provides enumeration of available pixel density units. + /// + public enum DensityUnits : byte + { + /// + /// No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// + AspectRatio = 0, + + /// + /// Pixels per inch (2.54 cm) + /// + PixelsPerInch = 1, // Other image formats would default to this. + + /// + /// Pixels per centimeter. + /// + PixelsPerCentimeter = 2 + } +} From 9210d4cc3562f4529bc6545658f6690cf06f1d91 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Jul 2018 22:07:44 +1000 Subject: [PATCH 02/14] Can now preserve correct resolution for jpeg and gif. --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 36 ++++++++++++++++--- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 32 ++++++++++++++++- .../Jpeg/Components/Decoder/JFifMarker.cs | 5 +-- .../Formats/Jpeg/JpegEncoderCore.cs | 10 ++++-- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 6 ++++ src/ImageSharp/MetaData/ImageMetaData.cs | 6 ++++ .../ResolutionUnits.cs} | 4 +-- .../Formats/Jpg/JFifMarkerTests.cs | 4 +-- 8 files changed, 89 insertions(+), 14 deletions(-) rename src/ImageSharp/{Formats/DensityUnits.cs => MetaData/ResolutionUnits.cs} (88%) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index fc73f55a1..553748f95 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -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,14 +516,42 @@ 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 = ResolutionUnits.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; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index e4737f3bc..f5c6ac353 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 == ResolutionUnits.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/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 591af6344..af0ceea10 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 { @@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.MajorVersion = majorVersion; this.MinorVersion = minorVersion; - this.DensityUnits = densityUnits; + this.DensityUnits = (ResolutionUnits)densityUnits; this.XDensity = xDensity; this.YDensity = yDensity; } @@ -52,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 01 : Pixels per inch (2.54 cm) /// 02 : Pixels per centimeter /// - public byte DensityUnits { get; } + public ResolutionUnits DensityUnits { get; } /// /// Gets the horizontal pixel density. Must not be zero. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 1310d90d2..aee5fed8f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -210,7 +210,10 @@ 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( + (byte)image.MetaData.ResolutionUnits, + (short)image.MetaData.HorizontalResolution, + (short)image.MetaData.VerticalResolution); this.WriteProfiles(image); @@ -425,9 +428,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the application header containing the JFIF identifier plus extra data. /// + /// The resolution unit of measurement. /// 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) + private void WriteApplicationHeader(byte resolutionUnits, short horizontalResolution, short verticalResolution) { // Write the start of image marker. Markers are always prefixed with with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; @@ -445,7 +449,7 @@ 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 + this.buffer[13] = resolutionUnits; // xyunits // Resolution. Big Endian this.buffer[14] = (byte)(horizontalResolution >> 8); diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index a360d5477..be2af7a3a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -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) { @@ -423,10 +424,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ? ((Rational)verticalTag.Value).ToDouble() : 0; + byte units = this.MetaData.ExifProfile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolutionTag) + ? (byte)(((ushort)resolutionTag.Value) - 1) // EXIF is 1,2,3 + : byte.MinValue; + if (horizontalValue > 0 && verticalValue > 0) { this.MetaData.HorizontalResolution = horizontalValue; this.MetaData.VerticalResolution = verticalValue; + this.MetaData.ResolutionUnits = (ResolutionUnits)units; } } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index af3cc5f5f..9a7e2d179 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -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,11 @@ namespace SixLabors.ImageSharp.MetaData } } + /// + /// Gets or sets unit of measure used when reporting resolution. + /// + public ResolutionUnits ResolutionUnits { get; set; } = ResolutionUnits.PixelsPerInch; + /// /// Gets or sets the Exif profile. /// diff --git a/src/ImageSharp/Formats/DensityUnits.cs b/src/ImageSharp/MetaData/ResolutionUnits.cs similarity index 88% rename from src/ImageSharp/Formats/DensityUnits.cs rename to src/ImageSharp/MetaData/ResolutionUnits.cs index ce30bd06c..7dfd62de8 100644 --- a/src/ImageSharp/Formats/DensityUnits.cs +++ b/src/ImageSharp/MetaData/ResolutionUnits.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.MetaData { /// /// Provides enumeration of available pixel density units. /// - public enum DensityUnits : byte + public enum ResolutionUnits : byte { /// /// No units; width:height pixel aspect ratio = Ydensity:Xdensity diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index 332899e8d..e7d8845b4 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(ResolutionUnits.PixelsPerInch, marker.DensityUnits); Assert.Equal(96, marker.XDensity); Assert.Equal(96, marker.YDensity); } From 18fd82039832e608aa8e86cc04245a38cabb11f0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Jul 2018 17:40:09 +1000 Subject: [PATCH 03/14] Properly handle png resolution. --- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngConstants.cs | 6 +++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 35 ++++++++++++--- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 44 +++++++++++++++---- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index be2af7a3a..8e2db4e8e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -425,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort : 0; byte units = this.MetaData.ExifProfile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolutionTag) - ? (byte)(((ushort)resolutionTag.Value) - 1) // EXIF is 1,2,3 + ? (byte)(((ushort)resolutionTag.Value) - 1) // ExifTag.ResolutionUnit values are 1, 2, 3 : byte.MinValue; if (horizontalValue > 0 && verticalValue > 0) diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index ff25e26b7..df0e16a17 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -41,5 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// The header bytes as a big endian coded ulong. /// public const ulong HeaderValue = 0x89504E470D0A1A0AUL; + + /// + /// The number of inches in a meter. Used for converting PPM to PPI in . + /// One inch is equal to exactly 0.0254 meters. + /// + public const double InchesInMeter = 1 / 0.0254D; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 04d4f057c..f95b9970a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -233,7 +233,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 +307,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); @@ -396,9 +396,34 @@ 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. + // Conversion note: one inch is equal to exactly 0.0254 meters. + int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)); + int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)); + byte unit = data[8]; + + if (unit == byte.MinValue) + { + metadata.HorizontalResolution = hResolution; + metadata.VerticalResolution = vResolution; + metadata.ResolutionUnits = ResolutionUnits.AspectRatio; + return; + } + + // Use PPI for its commonality. + const double inchesInMeter = PngConstants.InchesInMeter; + metadata.HorizontalResolution = hResolution / inchesInMeter; + metadata.VerticalResolution = vResolution / inchesInMeter; + metadata.ResolutionUnits = ResolutionUnits.PixelsPerInch; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 69f04979c..6aef2a4d9 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -8,6 +8,7 @@ using System.Linq; using SixLabors.ImageSharp.Advanced; 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 +599,46 @@ 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. + // Conversion note: one inch is equal to exactly 0.0254 meters. + 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 ResolutionUnits.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.HorizontalResolution)); + break; + + case ResolutionUnits.PixelsPerCentimeter: - this.chunkDataBuffer[8] = 1; + this.chunkDataBuffer[8] = 1; + const int CmInMeter = 100; + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution * CmInMeter)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution * CmInMeter)); + break; - this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); + default: + + this.chunkDataBuffer[8] = 1; + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); + break; } + + this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); } /// From 95e77e3ba3034596abb2eac7a1bcca852ffb8a10 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 7 Jul 2018 18:03:14 +1000 Subject: [PATCH 04/14] Use UnitConverter to abstract complexity. --- .../Common/Helpers/UnitConverter.cs | 73 +++++++++++++++++++ src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../Jpeg/Components/Decoder/JFifMarker.cs | 5 +- .../Formats/Jpeg/JpegEncoderCore.cs | 23 +++--- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 8 +- src/ImageSharp/Formats/Png/PngConstants.cs | 6 -- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 ++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 22 +++--- src/ImageSharp/MetaData/ImageMetaData.cs | 2 +- ...olutionUnits.cs => PixelResolutionUnit.cs} | 2 +- .../Formats/Jpg/JFifMarkerTests.cs | 2 +- 12 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/UnitConverter.cs rename src/ImageSharp/MetaData/{ResolutionUnits.cs => PixelResolutionUnit.cs} (93%) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs new file mode 100644 index 000000000..9a7d25c55 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 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; + + /// + /// 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/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 553748f95..462f09897 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -535,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 if (this.logicalScreenDescriptor.PixelAspectRatio > 0) { - meta.ResolutionUnits = ResolutionUnits.AspectRatio; + meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; if (ratio > 1) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index f5c6ac353..ea507c781 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Gif ImageMetaData meta = image.MetaData; byte ratio = 0; - if (meta.ResolutionUnits == ResolutionUnits.AspectRatio) + if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio) { double hr = meta.HorizontalResolution; double vr = meta.VerticalResolution; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index af0ceea10..f153ce062 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -29,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 = (ResolutionUnits)densityUnits; + this.DensityUnits = (PixelResolutionUnit)densityUnits; this.XDensity = xDensity; this.YDensity = yDensity; } @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 01 : Pixels per inch (2.54 cm) /// 02 : Pixels per centimeter /// - public ResolutionUnits DensityUnits { get; } + public PixelResolutionUnit DensityUnits { get; } /// /// Gets the horizontal pixel density. Must not be zero. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index aee5fed8f..80f21c65d 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.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,10 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader( - (byte)image.MetaData.ResolutionUnits, - (short)image.MetaData.HorizontalResolution, - (short)image.MetaData.VerticalResolution); + this.WriteApplicationHeader(image.MetaData); this.WriteProfiles(image); @@ -428,10 +427,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the application header containing the JFIF identifier plus extra data. /// - /// The resolution unit of measurement. - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. - private void WriteApplicationHeader(byte resolutionUnits, 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; @@ -449,13 +446,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[11] = 0x01; // versionhi this.buffer[12] = 0x01; // versionlo - this.buffer[13] = resolutionUnits; // xyunits // 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; + this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits + Span hResolution = this.buffer.AsSpan(14, 2); + Span vResolution = this.buffer.AsSpan(16, 2); + 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/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 8e2db4e8e..409908eac 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; @@ -424,15 +424,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ? ((Rational)verticalTag.Value).ToDouble() : 0; - byte units = this.MetaData.ExifProfile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolutionTag) - ? (byte)(((ushort)resolutionTag.Value) - 1) // ExifTag.ResolutionUnit values are 1, 2, 3 - : byte.MinValue; - if (horizontalValue > 0 && verticalValue > 0) { this.MetaData.HorizontalResolution = horizontalValue; this.MetaData.VerticalResolution = verticalValue; - this.MetaData.ResolutionUnits = (ResolutionUnits)units; + this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index df0e16a17..ff25e26b7 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -41,11 +41,5 @@ namespace SixLabors.ImageSharp.Formats.Png /// The header bytes as a big endian coded ulong. /// public const ulong HeaderValue = 0x89504E470D0A1A0AUL; - - /// - /// The number of inches in a meter. Used for converting PPM to PPI in . - /// One inch is equal to exactly 0.0254 meters. - /// - public const double InchesInMeter = 1 / 0.0254D; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f95b9970a..fd2c636bb 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; @@ -406,7 +407,6 @@ namespace SixLabors.ImageSharp.Formats.Png // 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. - // Conversion note: one inch is equal to exactly 0.0254 meters. int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)); int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)); byte unit = data[8]; @@ -415,15 +415,14 @@ namespace SixLabors.ImageSharp.Formats.Png { metadata.HorizontalResolution = hResolution; metadata.VerticalResolution = vResolution; - metadata.ResolutionUnits = ResolutionUnits.AspectRatio; + metadata.ResolutionUnits = PixelResolutionUnit.AspectRatio; return; } - // Use PPI for its commonality. - const double inchesInMeter = PngConstants.InchesInMeter; - metadata.HorizontalResolution = hResolution / inchesInMeter; - metadata.VerticalResolution = vResolution / inchesInMeter; - metadata.ResolutionUnits = ResolutionUnits.PixelsPerInch; + // Use PPC since original is in meters. + metadata.HorizontalResolution = UnitConverter.MeterToCm(hResolution); + metadata.VerticalResolution = UnitConverter.MeterToCm(vResolution); + metadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 6aef2a4d9..b8a5c22bb 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -6,6 +6,7 @@ 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; @@ -609,32 +610,31 @@ namespace SixLabors.ImageSharp.Formats.Png // 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. - // Conversion note: one inch is equal to exactly 0.0254 meters. ImageMetaData meta = image.MetaData; Span hResolution = this.chunkDataBuffer.AsSpan(0, 4); Span vResolution = this.chunkDataBuffer.AsSpan(4, 4); + switch (meta.ResolutionUnits) { - case ResolutionUnits.AspectRatio: + case PixelResolutionUnit.AspectRatio: this.chunkDataBuffer[8] = 0; BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); break; - case ResolutionUnits.PixelsPerCentimeter: + case PixelResolutionUnit.PixelsPerCentimeter: - this.chunkDataBuffer[8] = 1; - const int CmInMeter = 100; - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution * CmInMeter)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution * CmInMeter)); + 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.chunkDataBuffer[8] = 1; - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); + 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; } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 9a7e2d179..af5cc6191 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Gets or sets unit of measure used when reporting resolution. /// - public ResolutionUnits ResolutionUnits { get; set; } = ResolutionUnits.PixelsPerInch; + public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; /// /// Gets or sets the Exif profile. diff --git a/src/ImageSharp/MetaData/ResolutionUnits.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs similarity index 93% rename from src/ImageSharp/MetaData/ResolutionUnits.cs rename to src/ImageSharp/MetaData/PixelResolutionUnit.cs index 7dfd62de8..899dedeb8 100644 --- a/src/ImageSharp/MetaData/ResolutionUnits.cs +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Provides enumeration of available pixel density units. /// - public enum ResolutionUnits : byte + public enum PixelResolutionUnit : byte { /// /// No units; width:height pixel aspect ratio = Ydensity:Xdensity diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index e7d8845b4..b2dc3534d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -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(ResolutionUnits.PixelsPerInch, marker.DensityUnits); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); Assert.Equal(96, marker.XDensity); Assert.Equal(96, marker.YDensity); } From 965bc1037debd8d662a4657eb1913a7d257fbb2f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 00:02:30 +1000 Subject: [PATCH 05/14] Read/Write bmp resolution --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 20 +++++++++- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 38 ++++++++++++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 20 ++++++++-- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 16 +++----- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 11 +++++- .../MetaData/PixelResolutionUnit.cs | 13 +++++-- 6 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 20175613e..d690a3a75 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -59,6 +59,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 +108,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 +162,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); } /// @@ -518,6 +523,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } + // Resolution is stored in PPM. + ImageMetaData meta = new ImageMetaData(); + if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) + { + meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; + meta.HorizontalResolution = this.infoHeader.XPelsPerMeter; + meta.VerticalResolution = this.infoHeader.YPelsPerMeter; + } + + 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 a4e61f910..80fc6330a 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/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 80f21c65d..ada33f2b8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -5,7 +5,7 @@ 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; @@ -448,11 +448,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[12] = 0x01; // versionlo // Resolution. Big Endian - this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits Span hResolution = this.buffer.AsSpan(14, 2); Span vResolution = this.buffer.AsSpan(16, 2); - BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); + + 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/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index fd2c636bb..38c550344 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -411,18 +411,12 @@ namespace SixLabors.ImageSharp.Formats.Png int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)); byte unit = data[8]; - if (unit == byte.MinValue) - { - metadata.HorizontalResolution = hResolution; - metadata.VerticalResolution = vResolution; - metadata.ResolutionUnits = PixelResolutionUnit.AspectRatio; - return; - } + metadata.ResolutionUnits = unit == byte.MinValue + ? PixelResolutionUnit.AspectRatio + : PixelResolutionUnit.PixelsPerMeter; - // Use PPC since original is in meters. - metadata.HorizontalResolution = UnitConverter.MeterToCm(hResolution); - metadata.VerticalResolution = UnitConverter.MeterToCm(vResolution); - metadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter; + metadata.HorizontalResolution = hResolution; + metadata.VerticalResolution = vResolution; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index b8a5c22bb..df0dffa49 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -623,6 +623,13 @@ namespace SixLabors.ImageSharp.Formats.Png 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; // Per meter @@ -633,8 +640,8 @@ namespace SixLabors.ImageSharp.Formats.Png default: 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))); + BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); break; } diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs index 899dedeb8..ce848004f 100644 --- a/src/ImageSharp/MetaData/PixelResolutionUnit.cs +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -9,18 +9,23 @@ namespace SixLabors.ImageSharp.MetaData public enum PixelResolutionUnit : byte { /// - /// No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// No units; width:height pixel aspect ratio. /// AspectRatio = 0, /// - /// Pixels per inch (2.54 cm) + /// Pixels per inch (2.54 cm). /// - PixelsPerInch = 1, // Other image formats would default to this. + PixelsPerInch = 1, /// /// Pixels per centimeter. /// - PixelsPerCentimeter = 2 + PixelsPerCentimeter = 2, + + /// + /// Pixels per meter (100 cm). + /// + PixelsPerMeter = 3 } } From de02be2e5eabfdf168c78a7538e9c6f86b6307b6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 01:05:16 +1000 Subject: [PATCH 06/14] Fix broken chunk test --- .../Formats/Png/PngDecoderTests.Chunks.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 6f04ba651..dc29b1949 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) From 692d539b787c31e199634059c9d0e2db7ce4c1ad Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 12:34:14 +1000 Subject: [PATCH 07/14] Add gif tests --- .../Formats/Gif/GifDecoderTests.cs | 88 +++++++++++++----- .../Formats/Gif/GifEncoderTests.cs | 36 +++++++ tests/ImageSharp.Tests/TestImages.cs | 5 +- tests/Images/Input/Gif/base_1x4.gif | Bin 0 -> 1620 bytes tests/Images/Input/Gif/base_4x1.gif | Bin 0 -> 1620 bytes 5 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 tests/Images/Input/Gif/base_1x4.gif create mode 100644 tests/Images/Input/Gif/base_4x1.gif diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index ceb60ae5c..b15e8ff33 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -13,7 +13,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 +21,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 +71,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 +134,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } } - + [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { @@ -169,7 +215,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 2b9c11fb0..e9104ef8d 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/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b0bdad8e5..99057a434 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -176,6 +176,9 @@ 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 { @@ -184,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests 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 0000000000000000000000000000000000000000..b5d481fee1492de7f886fcdb186c12664d5f9200 GIT binary patch literal 1620 zcmcJ~{Xf$Q0KoCDnVIBaX49<;`5Lwen-tJKvQ_S(%hPiqL} zxp^3FSccgs)`nzTo^o79o{nZ5iMm5MUH`;=e*gUN_Q#&_^dQjy8c=1g1pojT42D1; z`uh3?1_p+PhDJt478Vu&@Xf}?#@5yrfk62B`eHB`e}DgP06-uRh(uycOiWTz5`)2D zGMOnUDZn?NxVV_l=L>~GsZ^>|D*O8ShKGk$D%ITF-2D8!Mx)u>-2C=Gcv_5;@)-HJ=m9iTAn$l%Ga)t7BOJ{ja>Z{)0hes6skrhLKyqlODH5yA3 z+rL-OYZey&T3Y_F@=+t6{?mMJbz}3(*Nso zLBzq|toGk-T0x!?xJgP^ljY%vl?hzer{z$PamdC*t?S=E)JJrJB%1hQnC+@VDvrXmb9hsBa zi6`3gocE*TT2rsugauw9XI55s$+N)`#E4Wn>v_!tCTq3*6Nsp@h2GE!QuFbSdV3tN z`azoLiyYp^>{_DEQ0RHIsNLPUN-*H2x@oc<;_iJzEXw;`_AK-mP~W)Jq}k zhC8=9y}*%c>qXECaz6l^JR*Bho|(8LggU%?*{Pj69xJukMl|jM`HMa<#}ddZudte{ zM(2>6O(t7uRDGuQF%k0QgjUtL;$*uU!9YyLX;QnQaO+I7$~ih^tMv+CgY zfP>^yYz=t4+4$uo0XZU$@L|^DgCU>S+0O%SBNIY05`5w(wbXw5$|g(mZ&vU-kJ(2A9&RR9C8gE>1#m+|_E@l}`mv zuAUI1F8{&5Hsdw>uM6s!L&>e}gR{#qnJ1u4CWh8}M}ARU((5O=|6Zy+$pj;Y2Ra#;LZ!m>?Vwpw|Q zQcG$%aet3fk9&`KU5wMr`%HB|^@8uny~m4^OwG)C+_%Kt`Lfro6oz*sy8}+5hkCyF$Z|oQs&`714%o>JuuYX1D*Bx&);Q?$ySY3#{nmbL;kZeWZ>t~-mT;_54|lI~cMCchY`tZB z0kxL>kX>o-fK>!i`vcm0REO{0>*Ye@$P$~y)vT?kKQ{qv0A%e=dU9~|dT{E#+n#IL za^f7@?)#wj&4CLm54xr+Ja4Y$W-V1B&R~@xl=?@!;3|7ujq=j-CbBrO$^~2K?mN1c lmnZ`tic6)qc2+Hu^~O~(N*f|414Tmn{R6f z<$LpGxM3M)qgWe~ZTXhtGV<+c#*wHyl#i}|;$FYMet7!h&Uku|=>Q$5vR?%N01O60 zAP@rs14BbY7z}1)WMpAs0RZ1@Y;0_8ZIMW%udgo_i}m;S{{{d=B9TNQ#l*xUB_%PL zOcslkl9B>^1B#1_1pU!LKP@-lI^eUbl3X*QCDc{e5^u*&kUk^vAo2$x)-R zG>QFt&AfJD@vo)j4=W$FlIcIq=TJl-_lkol(~JMdqxa1uSPpyyxW9w9))-rQZvKvFLHXL1KvSmLFv=w2SyVt z;f%D0{{(=IcTcg!>{ymA=zg|w18QHzWceG&knboNGkw4SeNNzaTNufMIDsj>_CPW- zH61P`o&)RjhghUJTA-)3sSnoU1L&5449Qo52X(IpHw7f-nG8NGUOQP{vAgRP$EZs) zeI7&_{LO0r?WPsfDWRLRbTwHKj$E0*cYRt81sR8IOhj%>%r=TX0oFc`L9J64v*LXu z-tcDiWj|2e!dshQx6QZxKY|Dy^dP+#*Tbnh>#-PS7**3x)8BS$!RyVF1N?v-aTobl zo9d&CsC^NQr^rhs0c5r|Y%tQytiWRCLfS>YF|-}<%d9DQTAax_ai|9tj*Wz4INm8E zvtg!1vzT1MoI}W?Smk9HI4ovIM>dgA9gRkmnQBZyl*h1~3qt{&;2SYVcAUG0SDK>< zBk{=~%1~$__as-8RC?vOxT>sYwSbs2hod{1{m@=G&%0AC8m_!#O>*K*dpX-wj^2?w znVoo|GtYfLN~tyVs!dqn7jkFib(cIF96`)Tm9xIrOknbkwtoVVb+*tOxzssM69s}wdmzq@j+CyAW z!q#x-R;L$uN^QLuT0!XtfRjh$FUm6$mqbv9cP~42QpaOuHrvR?A3*-%53I2S%E~L8 z_UaL-T}x1Bn>`Y-qTDLllmE?Kie$6vE@(8Q3e!xT%um z*e#4aHnTcfe%*3WTtS}l5sxi=Rv#@ZC6qJ|UhG$Yev-kXwyHFh>Ys~~&|G(ohJNK! z!IP^eB46xqoh+avmW;?@przgH;m6jxs#t8o-DbB zp%zr*YRPAHj1)$FX^Nv|dk`BthYBEmF>WTqSM=6r%n`cRQK&%ju5b@5t@nUuobtg= zxCdOs*WY#H%HCZD+T(h7Fp@nEy8Lb~55c&#A6GbTQsmnz41*^eYt+Zz>)hRfNd{YQ z* Date: Mon, 9 Jul 2018 15:30:14 +1000 Subject: [PATCH 08/14] Add png tests --- .../Formats/Gif/GifDecoderTests.cs | 1 - .../Formats/Png/PngDecoderTests.cs | 47 +++++++++++++++++- .../Formats/Png/PngEncoderTests.cs | 34 +++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 ++ tests/Images/Input/Png/ratio-1x4.png | Bin 0 -> 404 bytes tests/Images/Input/Png/ratio-4x1.png | Bin 0 -> 344 bytes 6 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 tests/Images/Input/Png/ratio-1x4.png create mode 100644 tests/Images/Input/Png/ratio-4x1.png diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index b15e8ff33..6d2a74c03 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; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 66e4f39fd..d2672fe24 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,8 @@ 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 +68,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 +227,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 540fc0716..62de45064 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/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 99057a434..9c9848d24 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 diff --git a/tests/Images/Input/Png/ratio-1x4.png b/tests/Images/Input/Png/ratio-1x4.png new file mode 100644 index 0000000000000000000000000000000000000000..559e5261e705daf7a562a18eee445ccc35990e97 GIT binary patch literal 404 zcmV;F0c-w=P)nGg^KQwW7n2!*3?6#5{%QaA_=g+OQ^R1n-BcAPeo8I7%7X;;=;1%j1i zZ)bCl8v&97rVft|P_iC!Ccs>)Nfzav-)4vs=ljCt>^(xPQBFB!f~}2 z33c`L-70SCvR3s|4U=$oHH9TFzjO1%!~RfY+0z-VM(^3 zbZpsM^d4*^09(kX9^w*ZmI5XJmB^)Ql!H<7e6ZY) zc`+QeUEqwtD6lI*MoSDvI5{}WC480&PtuOH*-2AVaV?E<`JCD_tpv-j#9PIaBFEN! ya5NvsJwe(+v{{WEX}38N*-?K$b^NQk+{YgmGj*nOAn>;U0000d_tM6?Z=$-SxY}-ICRK&4y|1 zVwc#f!dHA1xV88oBj*}@g$RWy_hMZ-KIm-D*-?;qSET3(i^4(8Km2Jkn4dfq@Se&3 zTKj@moXkY~bFu#0PPnZ(e1e0$_)enlnWhXC=IqbUUkI-WEMadnm^fjTNZTC0CDs-P zO1_Hd{`ll}k%^K0x`I<0XS2iy+u$b!SFTK5^fGI1n`4Un+K*`hy-NEv><+L^T&{2S kcJ9aK_IoSqvM<+joQ+wPvG4a1ptl)3UHx3vIVCg!05sc)VE_OC literal 0 HcmV?d00001 From b176a0ec17b1569471b91855af70f85c5bdc91fc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 16:43:48 +1000 Subject: [PATCH 09/14] Add bmp and jpeg tests --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 11 ++- .../Formats/Bmp/BmpDecoderTests.cs | 52 ++++++++++- .../Formats/Bmp/BmpEncoderTests.cs | 49 ++++++++-- .../Formats/Jpg/JpegDecoderTests.MetaData.cs | 88 +++++++++++++----- .../Formats/Jpg/JpegEncoderTests.cs | 65 +++++++++---- .../Formats/Png/PngDecoderTests.cs | 2 - tests/ImageSharp.Tests/TestImages.cs | 16 ++-- tests/Images/Input/Jpg/baseline/ratio-1x1.jpg | Bin 0 -> 34674 bytes 8 files changed, 221 insertions(+), 62 deletions(-) create mode 100644 tests/Images/Input/Jpg/baseline/ratio-1x1.jpg diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index d690a3a75..c135ab6d6 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; @@ -524,13 +525,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp } // Resolution is stored in PPM. - ImageMetaData meta = new ImageMetaData(); + var meta = new ImageMetaData(); + meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) { - meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; 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; diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index b994af056..09c3d1545 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 8d29536b2..d887d23ad 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/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs index 10b098d92..f217f0df1 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 911812ebb..a31ae37b7 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.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index d2672fe24..54f3e397c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -20,8 +20,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; - - public static readonly string[] CommonTestImages = { TestImages.Png.Splash, diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 9c9848d24..142b923ed 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, FilterVar, VimImage1, VimImage2, VersioningImage1, - VersioningImage2 + VersioningImage2, Ratio4x1, Ratio1x4 }; } @@ -127,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 @@ -182,8 +183,7 @@ namespace SixLabors.ImageSharp.Tests 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"; 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 0000000000000000000000000000000000000000..0162ac9aedba988372e8bdf055c6862e47c3db3e GIT binary patch literal 34674 zcmeFYd03j~);CI;rb#o_IAn0z)m=3jH3sJab{matqEdrMoD)qjYScL43{BHC&eW|& zQISR?C@6*~A`&%&L!u~A#0kNwRYb)Za6%8+*SWrPuJ^p}KKoqX_kP#;W8W&m{X9Gm ztb5&S{nl@-wLh}|2jHt~SAVz)IB);}IH33e?DGIu00%$&G(J5ZRE*ER`25p2^2Ha2 zzWDOU(W74;`SQ!7$4?zUdhFz}FTXsYbmHWxuf9_H>ge&)%BR0lRy_ae(?t$^dgsB< zk0{>w)v+&+DQ5paj{VmFr6UIq9KL_>fDYg@r2_|*4(zuBG!-B6)8_*GU*qtRFTVWz z(1FhmDh|7SrT7*H4<0yl=-`)!4t;U(OU2jv?BM5zzEC=RtuN^6h8TH~p%> z?)yB$82$6#S6`}?za5y`9|Ig$T>LYogGvA}V65nW+~VI){w;xjOW@xU__qZ9 zErEYa;Gat1GaG@t+}4a!8;IOnnn-9W&Q_68g1o57hRgs1#^SxYQEq#?EOaWNsLZn1r20prR|oO}OwpewO9=B2tS578i>_<`U|M<6Ca_t#7~Nh(-i6yaKN#WjAWrZOF)2 zHWRL=M~)THT;CHA_BLtcCkU4K%yd4?X)LfLf0gq~yR<9b_%0_`2+rWZtaPu5mL|<6 zPL3a{wEw5F{r}g&gQwkLaA|(iaGGWRAp~a-v zj+96EkuDJI5V21l_vCvM!Wd>snlD*rO+9UUsM}W0QmIUuL8h#{F6MQ;nWvkdyHLfQ zogbsYwTEhS0a@*?p7T#4amhENW5L&A&&QT1_nK9Qnsegyf`m?0vwY>fkh+z}H^=QT z{`}(3t8I+9lv;>;54j~-UvF{P&tJna2IQ`!7?X;>_U|%F#GP6lz(`Y!N!=PA0jgiY zq>vangQGO%tEYy@Z7)Il0KM>`CB{-~)6y|pLW@(W%kfhoZY9Lg)r#{Ovt=@i)!sPn zcttT$5|oy<;tc4aT9-62X#s8U0*_VfkIVjyG=^0~9}RLY*@qr2>ApWohba{I0--nv zOA7ARakxuIm5O)yM2?tBqZ!u1uoZJ7H_xehkwVKO#;8uJy+AW%vtD33%lS!#zu#|3 zQSnup!2>Us-NC`(#nnSENf`ueu^6WHHmyug4+d7uvbJX4|N}KEH*WBY*T zsW#G)v)=hj;rWvw&&2Lh0(K+`2`*4RAvNSX3m6RM3Ugtx)`6fH!;``E22Sm( z)@(E%ell0a`8zRZY%Iw8QtZMq{(0)I8ib_Du+3$*G~%dKvDnp?5+ZG!&m5(3Bn-0- z?Ky%T*c-+M|H$UXn9SP}kXI4@PJTP|H4|?1Om}M>%!9>}-|bR4j=Y=6BCoB&^{O|W z4JI{Jk9%4vWs|$xe7S2PjlXE!D`ysPBKWE>Kd1`lAA-www72&C{jyu>ecY8Q-h$Jp zZDf*Z=xD4LsVOJ5pw6xkeDq!hX&-RCkK0@aSDVX51b;6yH8dd2xI3BE^v(I7e6w?M zX1Btv4XgKMcX(%4z6iDep2ny&CrX^A`B+7{eWQMUr;0^ zQ*Y+A*nL3xCU+|MPdT}c>zve=n;z}(JVr|yS$CEODa=~;=qk7@>cdko5q*Tz0zLrTKDn`UT(F07G^p1^HKTbtRn^6F;EIpS&7K7cAJ zS~s7X;!Mjzgq@7pL<%JoZxX1Zb4kq)C#LOsHa^g*)SlTU7H;m!SIQB|?#R8y7iF~$ z@_OmkT6CN->Qq7x=Vv}8q%`3IgK|hM%t8Z(Di6Cx<4=$u*nUALE`=X<9WQq6rreru zdKyC|ldyzP8ZwQNUW7keCU&1RNv;@SB46L*YTmhkM(XYfZgl^#o z$%n+3fhemRcdIGk9EBHc$WD|thQ)aFD+g#=tpk#^Zv6`@{@-=V>+L7Qpr^6T86y4; z^JJ%=8SB<4nRgeqCRf$qLcq377!oB;eeI_(uh!v%l{?-7TxeoO+zDtVeRGS0z%IFi zXGu-lU&mHG}ijIkKAOalYd+#n8b&6w$nWl5{CU8w)fB{ z>|M|qD#?iw# z_j{H~%2hq734@8MCQ-IKyNlppPpW``43PVQ=qCh=rtYJQ8 zh!pg;7)5h&=7uB%j3|4uq_%iZt3$S_)l=028{O56BvgT*sQAL}g^VrvJ>=a{ODl7- zi!q~Uc~WU8KGo0Kp00|)U33R(%F7H32_Z>kG+eMt|HfkJA}fRWl)73ALn=(52gk*Q zwRi06fxWlzM)$lBZzdN-{-8RACdVeM;(wleIBMs)K|TX7aOiKg zPhh`++frX9!A65Bnx9tJn;Qj`j#Yy1hHRJJ=~$cyLt-J|Q@3wAW|fNIUUQs|Ol_|3Eo1OE93GB)PDZ|iG^%ggW@Nx+S+6(UaGrMP1F zEm@|!5BNsOY@Klw{alo|JxhL4E=rID^bTG&o|+yb_RWmVXM_;I=ZbzEOA4m0*oME@ zaLpygBLoHMuB0WXMzRlQ)sL}9nkBU*L2j?HuXni_Hc7(w0c&Mi>cX-d@zdAaR}CY% z>Q!6pQ*Dp*54opoa*MD^C{A0%N3{Fm-HTI4X3uc2Eiw_f`jqU75rJrcTeZ9x2; z7(7wy2N?y|MAHlqShAgUmhg}5dCXdV1v3|EPaJUv^PR=8)y+*O<)eiK1$iZT@j#%C zct^HSQW(Lfgi@LbiHb;O6^oS=zQ4nG8kX5h*&2s=%bf2-WB!!Yk4M~2V;QX%&JQ=+ z>E;7;OCu1N=b=e^J>H zuC^dj7eI9xS^m2zo>5aMv6=Sq;jSb~=o%q8_p#J^abYXB(t94Y{(5QYgd2MwaI%vL z>3Ep!kt#}GWBNicwdQOsW!R4mu0eN@5v9gWU4f##KbHym8(nCdP3JbxhxLrV-k!_y z)Nx3hR36pb75)Ket^NJK%c}m9>mF=pL6u=pC8cTLU)C;(JBC2{VmFE1qpD=|$r&wV zD4PzO*otKh%awke62JSHt=!^X1WI(vugB`LHc^ zi@B>IDP^vQO$%*%dx1njpmI-mN0`(}AjtUYafk1Q*TaW}h*vwgVSBzVQ0+HqE-W_C zp7r~x_MpM!6Y5a)wqvS0yz#BI7i}8+{Z6A-6B7}8_;1VZ!Etv&W%t(U#9RD2{M+WK zVttuMmerY=lOONPs>V{S+1-H0LrD(nhp@2y;qrzZY0@))C}&bG`DAMST^WRR=OeizF36K!|Iah2C@z7#)Y zt!o+*FnVR%bmu4ML|d2NsS;8Q{3td^_Ah8ZS*wmY4b z2D7J`a22S1!YUgmI^BpfT|D-?NYDkHZ*1`-qpV4>d|Og&Q2Q>KY41w`_o$#Q)3yA(bVa;S_szQl z^=Ex83j!qbvXzycOxp+^tKb?-G~Fx9DH)kL1Vl;-a=)n7L+=?}jd{Qi3G+ zs{cY;=V3?_IQbRYi`Z9XdstfFy*57n*V(uztpQX^cXC6jp%ibf)jx~>o`sJ)}3b1CaZ_WH$&jQ3JCQHKw)S|+2Qv>P~$6mSFG&o@5U za@l+=N1vVHQ0v>3&vEWkKkV!W5XF_{ZnXU{M)NMWI4_d*-e^wPTw8K|A|TY$DzMzMvGHl z+WIV?|6;Vn4inwqs~pH>OMS2Hwz)>z&+5zhAds@Md1BLQ(=Ny_G%_$OBV=nWXY|im zMTqc6?G{fGqTQz{DnTx5)=|x`^D^VZ5}N(-fgq#dg=y>tp=g~_J^i*g5Rv+|bJ7AY zASUu0lWyzf?=R?|qL_r5!DH+UbtMS=(gk7HWZCYj23HAq?dZIzhgBQQqM62Dp!oN- zXmpgAt&DUM(CR!HJ|Mn0iYi)VAnPj4x14KO&ZY0d)FEMWJpFJH4-y&J3BpxQb1;Dzf89P3aj3d1 z6f03UMn9yLPS)FfK;;BR+;7(+Nv+7*kk#y16N|?A9go!Qax-RkPp5_7gU7Ba!mFCVC2#> zyekEK-hj}GF8*pWo6!A0z~2rotX<=(C)@Wd?!bcx`+%q9UoC`n($xjGYC*;K9UslT z^_RFoC&_f()e;)aWgl=+*ALwQ_l>tovT!4YQC-nVSuU8^a#WscQMvW@gU9XRvwo3r zq)_s6$CTiJpG~Agx90c}#iW(AExG#jL0YQ*)vnUoAZYs=s#g5vNN6Z#+M`{Uk8WuD z&T=Bj;v`{C7E0_ZJwMMXLk0YvG1k<}?i+k**fSUtITJu$_HBzME#Ny5i78(dVdhV4 zx9PB)ZLnMSvRWF?1h3UP#$fSJvwvQYx7vaUvf8=gtCK?}&U{i<(nW8)1RjtC&ON&n z*?o?AS+5uw4{0bplF@1?xmbdY@v~2YMvle@_Y75j|5Z^owRd?bGuH*(s4KK(cbrKM z0jl%L+w}9DmoLzxkRKMLZG!S4asBhrx;_E!ap;8h<2WZ+*SfCen>N|dVlgph**FrG z@k+#EGn4Rq1rg(z`t`>n_E&?lI`&e>NJ5|ib7b62cRj*o9SewJJd|{_clWiqnW$Ne zXL8vpJeuP$EZ`UCtmh|MMki`_h)i3E3+;V*8#BgKn{QCG)d44@6>YD{yxs-0v-1$? zq||=kT5|YJW>PH8g>8U!{_cZV@WwBYYe%=}z5ciUaJGOj>k41$nsjYOVE1D2bhLii zFqK~yCsFq3m5{Q*rv>IH&G}g&81q%>yG24|`XnL{%s12i5{LTI&JJ88n2dBwa%Lyx zyyCdRxGL^03Jcus=nT9(`YV4i{-b6apHhqSuTH|qnse^*UhSDXxksQ2^uy(uFB>b( z+(yQ1E%Vx=`qWO4fjhIsfp=}5?BS;tc6S;$J>IP!!tB0IBd7I$IV-syl)a!pwPXd97TVvocvE)QHj!+w0FJhV(nd zIcs_NGkiOMY%O2}`cy%VXyS^k&j8O;crV=kx=lWmPyVqD(%3^$L}FoZAOu8(sL0RF zQEx>bXBy*8Plf7GwuXYA_ch15Oh#v^9ZexyPU*MCv%CCk0eYwZ1^^sCQ;WOSA+mCf z%br1!^bI@ef*yvp$ry3FmetckI;acs;?U4MAkgT?o;Ex#*KfD9q`>3uUn}rn3(}h>JIz^oYc>+#jd;Ho?{mOVB1%DR*gEZC!x7UPQNxQhdgGdt$}q&uAD?Mio{4O~Z$gh)-!?=VMK;Nj#u zm+RVNjxcRKCfn|GS>QO_e!MMU-lrU7IKS^-egL~0nEyV={UWxp+Eb0)=a+=uP^VQ`(2;1nOa8O~vk z(;ZXHp*!a7-(kzbbJR_S`p#T01n&fuO4l%FJ!4#-Qy&cpw1?{^wNdU zET!*d*>rl?Y{qO)AJ9O-FNuow0f&^=(@9ljs;qNvc9+^`;VDu>1_`CKls0+v zr0F@ddQ4vc*=s9$>2Oqo6eXOnSzW5})5>abAZqIM zqtw^8YVVGOUK$GG66}+OrF~MyFK>Boya*kwO1r~V2dM-9T$}&rkDj_-7(QAa;m>eY zfeID2*LgYC7W1R@i(K{?PZ&8FpCX@zQX?_5BrVnP@^J-7h)Ll3nMaah@90<$+BwG> zxd;VGmGp(_J#E9wV3Dk-vw{b#A3AInmK4Auf!|>Wci0jV-%Rdn1x#rjrlZmnnw&aL zo3*}<;*JIF^~+q}(n2*`n}O-Q)=3~yFL@w*a!FM)#ZhY8E z?yQJlgL|AJ)Jxp+y`^XX+e!s-}^)VPFFvs&kxnw|2A1x)kCX`zo^VRq%LQ z*Ba-_*}>#w3DLCZ-GH?9c#><45sj|M}Zg_>{dP}ED% zKM_ahzMIZNzBDoNv_r8W{>XM?&6F^-*&ho*-x#DuU09!$&xdzOib8?z6FbXrp*Vb@ z4k3FbpT5x&gP$e&sm2XQS5s!W7B1Aoux*p)QjBTphe?8Eu-3pX-d%;%ed>`cj3}#c?Ui*Q9OAppNFCvU=(Le-%R$ORvH2uK-_A) zmhY*nN6q?8FU`V&&X=Lj8!^EBSGo@In|dk9Kv(Otn_WGvQjr_yVk#L>)Y*z zg#u#u{p@3|gt`kojChk~^p}M2E^wA^IGV-|kR49Skj;!AgC#2XxcehZd#W4=dLM9E zQ|WB>@4aSVEQ*px>~V}+oYqG3m=5}or#TCL0%lJv%p$^Xv?i1TWbG`)@%91R^M%)r z6fcne+=BJhFFPwTQ++hImj?yh68aJp(Lq}Br6dqCE-1ndMLsA1^KI!p&65i!g-hJ> zjo}9ZdH|p4e*#$x+56@_0YCru*pu&4kwbR%Rg?F|z5z~y&1B>-^WrPXwcPmVcj1`n58?%&sVbDjZZuHS58ELOZ zsI9Jjy4QO1sqrYyG0IgzE1d8^mFMqN&{v9M$c-n5x4p75q zOA^DHYh$-K*-kar<4|U1(G>8}XZm76H|`)Wb&T2^{|Fn6x0R?DUf$tI2x6%73gPRE z>EobppWa3rb~prit>W$)1sl8`ks3SS92S!6`lZr`^qOSK!)K!HyJgnWgoHsa;-RYp<8IGep1IZ>SF?P-bq4-m(bXDB!Y{uhZ-%?C z7cINEtj@T=Kv})tF_Dsh^~}V;*mu|7FW(C9GZiWvc|@=;@hu3tDCR0{kY=Jl-@88R z)xGcsILPTaC6EI~K7N+v@{3pi@s*W>V`j?R)ciW~*17-edaNTNg6! zZN<2-Ngfd;W(6qKLXK0^0(Sw{mu!1=DT(Hid0_6WZ3xLBl+O0=%UuQBg#P9K6x4tE z@6=OIPm_}t_XSbSh z+}`v7padFT_#)o__9o>YiIUDG51a=aaCbe0MFu?r!{AC`OSC0a-xsPx52z z9i1Qjbx?Bggt}`tSK>lW7)kI9d;h*AI)T7%zMxoORIbwHKS`z$ef6n@l|dWmZ+oVV zZV*aa0!fCebc}K@6S!79pR>`0M;50V`PFOO&B{Cg8zJZv2VD!aid@795ic8p%q(Bl z{B=A{u`6SfcfR&)p5z`E)Jb$b1aNuf*+RptaNCX;dFMA89nA?F*x4|W?z&fOI@ir1!@&k`OTu3Z z$qB`7q+vJ0gDP8W`;%bQhTgnq998c(^xlPai0YjQa_7ugD-Fhe@;pk*{itxnr@Y|a zHcUW`IHqxT{wb-WUkrE7u8EL|OgFcO&1E6eITtcsAxsi{ex*{qOrPCuvD`?KStnP^ zH`n@qzu)OKHP;x>RgJYXYht(uFsYeg+2MWaPdzKpt{OPh%UlI{?pnYI;SR<9S& zqUe^k|CRb#m?k~xAi;<7$|1&sODfk3%yPIa2Eqp%8hkL*mpUv?b)VuG#jaN_wy<;I zT3#yCqnK&e?*3sU#*iisAU^Gn#q-9mTZ0c+dsM8*zI;3qcPv+997m@ z`gXhN$D!6*9;895(ifK5{9>a!p{Ur*{8eJQZ~>VyYrXA~_@Xzs;joXz(nhGmkK=x0 zt&+1_v}q3b2f{GYTK;~AsX%0F;$e`I1fQ2NJAC_s)PR(tJ3rQV)y?u7(#y+h3TFus zP-dcB%{E!o6T!6(Iz0Itce}8$1`ffsHHhIcTNg;7d)e;u=NrjnzI2;HEG&tateL{1 z>!v+=(lT1)-!Z^>fx0;F-EvUGv69=qdwx|pF?9oqFwgETGIbz%a7PFW>X0#IvnupK^sF@Z)b{oeJs4NKvf@YmpGcM2-(C{@)Fs_-xoz;hM8*ry}D2Pi6 zQhi67Wkd*?CnhER$*vaoQvEyZrbdO z1G4~PQ>Y8PF&KAVPI!jTD+s_RNyfC4lP{*FMz7231($J;HC^!Ec8j=2c*64qV5B3%v{8~E!qs%2poj2{V!}3^Mh4n%vO|7v)~7; zpY$SpACM)-dE8ClmGzQdeG||@;I!$pp(d-gly#nUKM3_UQc$#KId^9p{pTY7D9xX| zXC9=8x_3w|`+&_Q`b%twKuy1fqiqxCUcXh4Vhr(0+v>(16A@96oMP{(I6o01I$>R^ z9LR#K(tY$eJ0~{Ok6{uylA1m6l!alnaL@P0CHjjk8fp*0%OxO+a}s;yJGfRTjkg#?;p{W(p>4(qMOo2J6-V ziHz*NenM36ifaR)`EPzlLBeGPd~!@=%PBWP3gXftzlQ`aHtqtq{B=${Pew@a8EuV(o3AGywB<$=CptQRxDa+MuxrL$Wi9Qe!%)G0w7DmlQ43?JA>Q`W8E!=UrHz{F zjKX=@;% zpgRtk&B<50=Z3WE^Rm?>nw;o3JFcreY!M{RNR$v6nw$H8mVC?ZyVRmt52vX4{soAe z(lpW4y#)O8#(21yM^eK!hx4Sv0Wj|U&svC1G9N@39}5k!IWPT_OO54&)ygE}StYqn zD!Ucfb?jOi<{bV=0vBfP$n_9?wM(iZSdilFRBjKMe3aYGb$;U!Huj`-LA75%eI5u? zy#r_G4~s%EV`O$$8aYR`tya#EoPY={h0eylZ`#Yk$A{L8S5bc^LSE3p{PsOsz!}Uh z?q=PU2q9wLE1q`3*+lB;=@b(!*%{Vj78>EtDR6VAqQd%jVRB^HsoGNJ5P7%FC3<-) z#v{hHiXP^36YP!dd1({B%Xlt$DaFj|d##0aao16OA4PR7!Qa251hS$)%F!uH+?i?k zFA#o4qSGx_lr?#TmzS*M!S#@`*Z-&pX!mO$wlakQfjr$_b1qC#P6q@k5K?K6wJ7qc z?ZcrH@I^7M0v6rTTwYqbv)iAUt-LL>UY9I!dZD6Y!hVx(yMxa?MuvdloF=PplvGon z6@=OqNzfxcWr3J=h(&6k{x5y5^$D!mK$7kU`X2Nh5#7^z`G@Hw!Hdg zOw;D0Sj@*h3koUn+(neQ_r`}#FOO4D3NDiiyr`(`0)6}FL)9KDEFc`(smQ(MG&fvi z8oaxkuW$|)9FBG0KvKF`kt=b0jIDZGd<*?Bt>ZH zyFPQ`$0a$pCNi5{y(C8K@NVet=%ZIP4Dz#v?6ZIH#(@nvlp%&4=5kQ-w-UtA=z$9!!0FZle?k>jG5zh zeo8R?-IKK6woeUY38-<}ZeveHjH`IZouO(JMrNJw62F^jNJ1D8rKI$=bm!~>KW>*}!0OAW$XN85-mui4;S&&~3JICy@U>>WjlJ;fNO{v9-)??pR1@Xh z^wOTWs^I~JQ{A?9;&uo)@WEr(UUv>`EQTd)79p#sfxS&rCCIrnLUEvjj*gDTPZ3c{ zO3y96PfHsBXxDxGkEZ5-!Ry0kJO)?H{j5xHa-*v{-@c~C=<7*7Oev}n2%!{3iG?G3 zDeM#_^_dmXOTr>W(=L|!(IRz74Pw1?DRNG+YN~!2h}24fg7mH~QgEuT99KprJYyHd zL6ylp-w&)t+?fpympiL#bu`VA>0zA=SM&BH7OUNPYB1S~w-~erKLDq~kV}3PL-fikpxOL+5 z-yh!Ji2NXYwJbwh+de6ujFGjl)s`UIPtc=leGQQx7G6+y>g$$!lWl|8d}FW9^u2pc z`SmZ)bu4c*U{cys<2LPT7iWWFTT0l{)3rj1>&ori)queUIo7&7KM zLRNykt-iAb{w@o45&g<5Z$^oGnp2tFu1VN?Qr&z({fo2P`vA)s^G)^uDCAhOZYFu% zh+rQqIsv`?$L#!>3hjd@_gqKU=!o%QkG;87gtyfd+paoU@O4qn48th^Ig=Ep$P~%r z+M#kE@3)*4bDkN7cx!rU3}b{ehB}$;lv_uiZbTCsE7t2+YXn{4*ewzzwQ<% zTPN9al-J|>^Zy86sfYXm>D3bP%eJ*ESC{xkiW-uc<+CaQ`+#cI%i(rG_iJ@AFOjJ9 z$NV)}DyX*={In?kQLEt8so*s+N2B~}?$z_XZGmYy?x#N0jH_^zl(dwT=GIO=Y5FI7 z_6wUqzv*<2Jd?8Q(zn?E{I=hFLIyF75v`jUizYD$c)~VA+G8wNrK%TOfA2H`r~k z#Zo41q9wY{b_oWA!4#wdBH%Fl?5^Ld)r38Dr~1DinF4KosNeCKkKlJin`bz*wLI)MRziD zHSZeG(NPI|aq{&JG;zVfkwxvrojQMsYDfZamF&t$1XL`KP=&7?@gJ&qQFO}rH8+D~ zHI5svh-U}UatCVt>LHtQhb*ph>{9ost%_`mblQkI6=XZIuGTT z-2D^%_i>1QQaov@NK)i3pdmXZ_-?d3x|DAbj%!KM<0GS%STlEccxxuodJ2oNHJGx- zrWZduP$Jekrk_rj@rR+OHIig;qSI-tm>Ltm^Ke`4o>^C2^nip+V?%7cu3pWp2%s_I zH5sJw$tnNz`s`@EkfJuEO$I_wPcLnj5|o~iG6=4g2Tt()xvhhOByPNzM+#OyI&2;| z?YQkjXDit!#1o5&Lw6q^udp6-iHHHYeptJ=4>&tH+*bGf*U6SY zzA4K>hAPlU9j}K&1$BSaX*HSO%6IWDN?*qtw9haYi&_P0_Q{87w*$(|_{q zR$}XN({>N?m5*{qPm-2JpUJScRCZw}cSi-1+|@}!=&4_jxn&F0eI!Rdn2SQ@~WSGe{LSz^8PNtCP(f*Jqb~kJ)_Qk+3!{uBL9m> zfUSS9Q+PR?BbFS`2vQ`#?hY%7jdpd@2>4OLQzRc5X^@3K3+!1PaLVM}uW{Pq8OXZ@F3?q4`SO1-~mMOfAhGL4Jv@$T%8si$z!`fj}$!qZF| zw-?s$X9n|pHD9)jBqf&VGL+s99og+NdOO?rYRC{5RR1za0UaHdTo7M$f+kAZ+^DNL zH_A(5){-M@vOW;wBU=t1JguW=$f*_dFg(_$HF^1C87U5l_%}xPS{^pr5PQ#9_yt7I z7j)ReomgbgS{T?m!??qJGk)f-BSQVoT9(x@e-A~o5&|9h=;uE6%us^Qa9RD%*L>hh z)8JaujM@$9kW;+E&2^l zNu~2QQCb=waB6t&Uy$ghQ%IU^3R-3~+0IEcq`7Arh6R>jT@?&%&*(J6)qPFTXJjU! z8ToUUL;d9PXK{4_gBJs&MYewImk$W~MN&__5K>@jAUQ9(Y1^YZybSzt?E~FqdszR2 zs1v2$Oy;>}ZrnfKuMx)Q(8XNkgSK<1x8&+n7?1fK6Y6i`dDkii<(?VgP&uLPc8?GU zsy`mxQM_suQXe@+3e+uKLP%?2Ni&vn(?BInpDz}vFTnMJjC?mR~9gy@WJ)$D4F8@F)=U0gQh=qU^02 zdob^@?&SSg5cEZFfNjDQnX?%mvQ|W@`zz{p@$Jh@RE(-Gl&@LeZQYZ1F6Z4J25ZY) zVqfBST@kgDG${xZAHW}*)hl=ytY~#&@YC^%{Q5j8^#VaJctB;k2FIj1IP>QGwBC>* zU1j&kUM#vYs%dMKX3udR}JYiJ{r`oo|dr2~W&YR$|u9EECL`Puv0VkMI z9p=yt!q+)Kjp=ae&ED`^ZFFb!d|3<5YG=^5CuDglj{d9U!m=EyC}^CSZ*Hun%%72j zhq-@X_kO5rOV-sB!%ck2LYShLuIbSjpIJzOhH{(9l;-l{5Q0rHA*Eb*$ODTd#o^Ws z+gV)O9xg9DwVGi?p#?>TyPh%f7>w56K$^`h!@6prAPHx@Dz`JvJ7ToKE+9UU>zHdf{?!Dnh_GxJ-%fyr9Kv~?WV zxbkA@3B5&8KoUKJ3w9@FKHVsR_Gx0* z2);s_pi1qIjtP#ANuWoto|msoU}3$t?{Qj_gG&`grAK<|z?0YRvx`v0vcCW9+YtZ& zbZ&H{7@>Bxvg(dICNn*hDO(!0hbk@wgh4^MO3J1miqeLDz*%)SBPZs2eN2h5_x*HD zN9URKEeIJhxFzy4{BrQSo;ILsoA#H4h?HV!Fz&%TG5~IyM~+nI`Vvpipq7bVb@IM% zl+7Z_9Q0B`@naOi7jG>w7P|r3u(9C5?7_7hL48Y}P?7X+H;ePdAgr7Xc2*Tk(2K&W z3McvF`vtX@^gfU(Y;vw)81gutV9i8<^4n!PDGXAZH|)`^MO;`Fuj|^V_n{`T73_Vc zIoBh_vTB_&6HxDD27`8CRM=X^FT*u!pD*I5m15(gEZ*yZsPL(gjHYFnN|)w5K}RQ) zlt`+c?nK=vl;utkIkVfN-q&QkUxkFL6R(YsyyfU7#_-lHK(7A~Jsu$zuo z!uZEV%s6iO;rz;ImQPPtJ3)FsYI+%t{sF0%nn0>1fb!o^&z`!*N|C$I^Rkor=C;{a z?yi)f&k2JcE7f`NwY}!pfcxQp`A?<#`4z7JAkIJpZ+z1`1R5C%kAaL9-|yh%!`2u9 z4n63b*yWV!LQA7_L5Cvyri?N|mJ11cIzwt0o1`j&gDCNcB#(x7O>!4+)VQRL`}{m% zF2_GY+C2^1@@G!FYI_H@!262UJ+$hi^!a$0v0%U~Iw*Y3DtQq0kT^~2e4eU#Rbk#hoF<>WKb5fdZ@jJ@mHf|}J~Fp!FD z-RKuV98~V|UQH+(aUa)~eA$4cvj+?K!V(Oko3P($Y1t=mLAgfs{?-n=W4Cusgr&m5 zVi#hge0`XaS?a2&-s-)zAYs5Sn-JtT#`%j&cGkH297R8=T?G;C*8JD#-4fNJzQ*Zm z$-*bg{m^ZP(ah@9Ta+Qx$-|@PPsC`7eQeVSe2?uNLy%;2eQh%_N3Y5sg^}J9;hk&8 zdO7CHG>h(#P09WFKxV&v!j2(IT(!Q_c1F3Kd%-)yfbL`|?pFXnN2tpur}vvQ6btIP z6^%3E^f30Iquq8OltgCy{s9A3Aewczf{ZWT0csPHuEOJQ zj8nzH7a`vjP~$C8q$Zaz zpgf0!xU!kZkeE}BZ?}r|hw9?pChEWX!e=0-EYuG=u?>39Nghm=Z^|OiFzTa-QkyQ*MFfAB!BZdXBz(FR{gEC@P_wTV7*q zXoNX$R!!cZ>+VW8e2l*_yYhO(v?c!S^Sp-tbocyko=kc9lDa~zL(KY@ z_hu!>`7xszQ^f|K9CNCUj$QyPK};Lm2e5LUx(#yH?2608lp59>{&IwwZA_&;*Z%7Jv1 zK9kS6kdDZs`~a1%n&j*Gn_b;pWv#Fqqn1F^QZ=75M%ji85ZsOZ>t3xs;zi8kH%@U^ zw`CiPI}(78=U4yF)lky+v-{yb<;j#!XJ2`3KtMOK1@nB`FC#1?VQ)-Bx8l(MX4x&m9Gn{`C>R%V<#!W(1*7iRa!F8Gb_~#v-#=e+>ZbCJs z34fDBSsqYQw(GT&2Kyw&X-ml>ZS)~4ZCajf|080JTzC18;%)L6kF4n?9ZSVsUyr}a z=mkv@;4oDEr`?Vit3zFd#Hw0P!K-1fy#-1VVa@%S|oq55@a zM&vBq8DwS=vnY1y%HNGq7G9tDshf($`Mar&bC(x$V(iNPkM_l2$3OJ1_?5SVoD%DsA>yjPSXk`AjASm-~@um7{X90gJK{; z2x9_5BuD~52w@K1L-%|8`t;k+t@q3A>JRnpoREi{tbMZ2+H3v)zs=LVn$Zh;HC7fp zCwSzG5sve0I=$oshmmleM`LTMF3QSqzkGSVA@k3X2wDbxjzg|%NF$ZFh# zyJg(Oz9dwLhd0?H#YripNd+-f=$Wzh(}N*{=5DFMpYXa86QwJ!u5-0!nbCNCYH5;f zdFknN!l5fes}UJbu9-4(_vVtPDRy#fVqsEq(BjM*BU2`l$c;C|Oy0{IxT9H3u}oFn zsiC@9-e;kSO;L3mi}fpn9KeU)Z?dThU93g5Ys89x&K%`i7o5|LrrN@!(hDg zRL+^Ug^_%YZFeg>LliaZ?L0K=jTKY@3OEB&&)=GZIU1Y69v@3=Ccy1_CpVM799)fm zKB9fM|MCs8s&NQy4KokUJ}kN6F5r=NVK_nx$n+9JcP4#+WP#T+V9{?mMkS&*mFclY zNv9BZPfUKmuD@8H6cfC*(Cuu?ywz1;tt#ov4LoHI+=+}r~lOMzH|4YWpRGY z0z}=@eF=6c%o=}4Gylh&iiNS^z}ai#W!+ikg3O4?GSEbb1oH{}Gc3tXX-MMzpbG5%_n+Hu@XA3rzY_PeyeclnYxuz`lzzLy zeH<5wNubcYDT$*x^V9S&Ku?Aut_5gvq3(Mu>u;;(u3b+w^z$J+Y>?<>R?%O%<6q%= zqj_*+`x-vMyY3`qN{H{d>Hx0qYwst`KRQ3b!`9y{OO~z?YJYgzihbB0ox) zkZSP9=Tx&^zb6*e(ir;{fCF;;5C0tX1f8jB*)$b#@-bz7nMo9f?Pm3(N~<)~gPeCg1;MPSKP{>tpPfGq1-@^Y<&9ZAJ(s*O;2W)jU%y z^fU*d^`?C4vJK?^)`{)hdi9BC-102qSX_dO?;11OE+v{I4-sl-i;5@}H6kn3jyiH) zad9D-tv!9pFyk@&w?M~@z+^W2ro}*=Pye(P9_#!`;7|Cy^%$ZMD>W!ZjPu7e@^+UG zPai&PgXReG|cUB>WmblQ@ zgAZtw>I{nBlZmO^kn&P131_NQ*9s(M>2M3?EZZjLiysft?*{+mY_;beGy3P}D<@O& zj+t@GrBCPeY4;-fQ{qGg4T#2aR48eqLumJ95q;F&YL@sWT}D17^F`-lJ|1nf4+Lbv zJ~?`t@wCOZPZk)ry^?55fumL`^zDJ3FTcmQyREys#XcP}cbj}X6t$TPVoS=`3Q@L{ zxo5)Gd9fGkp?O|@xVhSq3RYY|H2pQ-eG0E$=zpkhtiB??YIUCZQ}X=Gv#fBV!bKxe z$TTWE2dvn7csU@Lj!^Mvi;RJxht+QEBNk47ILoDBPGw=L(kYe>=u}WXXXvTyq3VG7 z>fo64ziyxR+!7PxBFB&~V^f@-ZGzcDZXGzr>80U5Dtg6ES-JtfC4{0) zB%G%xCpY)ED|~#yCPwTpkWia@ZyCZODp^o9g%hD*UPw4QeJiWtgS>HR1-!L$SPlvI zZ@xNS5Ih~{cV`o8Y{+;{v6j8CM zj}{$=+*$O}(GFR7fUu@@wz!G8whK;~dm_7%E#a%(*@)eO3(`8|fMHhr*hpm~s$UTe zDhp?ePN&amYulE6v6g(W;AO;Ii|@InO;1h8Bs4hj>Z5Bn9$leyt83dq>qoxQ7`XS( zKfmAN{DhXjbzX1<)Ochgd(U-pkz>$?Wny(0~1F!oyMEIa?*}-X~K*9ql2gY5g$)ut7CX$FVD!g2R;?m z)>1&{GHt`;VI&+Q6A9;@=d;b=UTg@&uHIy;Bavm?C_DkIz%9AftCHM220>tK7I=0P z-(lvySAO4W;%G*t#5M9@WQdCHI)-{XQc07Sk~tR#rdun-h*~Q1tn0$mv%x0s+lrRk zZeOa{>_#o26L<_`DV+gSZFBYg#w;q{9*A6a*{cv;kuMrD90b=~J!S$TCgMqi3lcw) z5UM+;^tvMla?ONooJP~|kF1SN>9LcprcMW)vkUFM0+c8FmETF8p98ABm;e&?EcrW_K;n8c4FH?SDDC)6I^4l!Wkw0|`ne)J)Rj?i6>2c%6ov9L$Wx^%9$yt`DY8YL+8gpozE>6g~*- z!1nexS6+_>;_u094j+sWCzzWH`TgnbXxV1b>gpL*j4P(P_ZFu1s6HJ?($!A~EQOn~ zqZAwa`#Y!}?szMfDVUt4jY^_myR^1=e!IT|c7G|T^Oy+gvYKD{>142gtd`0m!>A4R zsl|Dy*_DSiC7n4$241%~<>cJP)`Q$XuiqHwD?ci?3n};JjaCYDyqJ-MU{?HO9jN5P zI^Snc(MIBDiNqi2*Un+inV0ow5-G$*f^ci2!PZW5M1|*D?{?>xgB8pF7`XSco;DTC!fMe_+|Y{(XSR)oCnm)GxsC6sR9)N^8E=YGYp_2rS95c9xf{4dnC!0<2lhs4|Mb?0n3pTd=0GcemTqUmiA#7v06& zSh%|0$W4Yg_V*OG+K~s!fvqUZb)jXm?be34=+vw&Soc{g7z_a)aiF6@TiX)E>!tgz zE1X^x(To}DP~U`gcADg2eR%2BIheP3%S5+OP97+yMx~55-1V)+TFs() zZz%j0@Db2jRR%tAE>GS<3^zM5E74Ho?$p5WDmv&D7{+|A8LYR@iu}=9>ZfR_y>S}# zpu;wI8BDIH@RzG10&M!PWE(;{YNVi^b+s^HqaL;HDvtkG%!PKPw+E0CA|4-y8ku4D ztQQ4vC?(%4anx0!WeCKI5MGAd_jK?t+e!*ZaE=b2%mIJz zI(N*~%&Y3q#a`d14_P08vcLOp@qNEhX8(Zgwn3-H_)e%c%FFZf{73QK**Bg(0Z=H^ zje0W=b78_NxJuR5$-E}!xIJ#S9N$h=?T~UKCA(S2x-sDaz1iWGL_j}-&VqUIRs~F0 zj6)X-=1~3Mr?&?dSTy#wGm&6GCvS>h?JB1H`K9f0snlaG$<{LIfKtd;?_AM)4mKDW zc1h088mdt7uYrQkmV)?6ZAuXWYzni?tdxx^1kjLSdD?y!t?TQphLLO04(NJZG#TsADewT ziFGHFt7Nok_->+3^{&oT<&yU`VU5$#jA`$!xgGYio)2cm)$U)NxaD;wI5fV4^UWlp z|67}OcT)sIXb{y zU#`0?d=N{eoY-hQbH1?_tttGuvfpb3(TcMF7MYCyB$PxX>x*m54!%}b`F!f2f|Ken zuAztA8DKK{W(;?(0PJw38s9ru^?xU>cf$7XJ_Mb{r!#=6^z;?dU%V9hKJAPc8!}&Z zHAe3h>#vDV=DB(*sZ@dpnhe4f0HA|hqn7+Xo<}5$lw~$Fo11aI?Mvr?XLtP-v=rhR z-}n)?uw^z_2CruM&b0I(N_nO{kAiR5qyd#0I9dI-laN%$kRdpQWsW zUH=+g=15t|rSAvb>WaO3k2yC|bV2M)Ha22IJ8-C~R`|1=Y#pcJAsV^axOyhh)$Z($ zuv|$~QyeCsleVxenO0k)M`(u3g9TeK|K3q>w!_ERpAs7!efqKyXhR^1%f)M(KbX$_ zI=}OShzSK?#QJNFhI@=3!+8QL;5E?V;x|_54jaLQD`nb%5Q@x&24uTwPkr7GmkiwD zsIr{_x#--wh#4K5o)h$^#K<_Tl#}A|z%-k_ce^nZ$dZq5qUYJE&$f3g;aV}CYZ;CNlOOlfU>8LAz%bV;J$2GqAiy$ga5Xr@NK=w~HN0vr0 ztr5OufS3*f96=cysCZ2qWSXxP<^CnM(|yon(bKJ-yf+PS#mmRK@C0(ca<^#`Ba_N? z-#NCHCEci8X)_BC*vn}UmFvwC($>Q_$IHe1S+sj!2t{w%T=zwj(y7%`G-IaPe4H&a zxLUFR6{qzDO}RpH=oSN_1Pq(g^VBVtHi9WhDW6Z=TwfPyO}2l|wds2E{AQKS zEv)FP2=i;3Xus%J?lo|l`uvhA+rcRzd+p4zxZace{qsKh!8~fnR#DT>Srs88`knd3 zxa>rgDzGaCCaoMTA5!`?VF6%9@mCE@2F_Ce;JDMsm_kn1?{ckOjk3 zlJnsA)K%o_Xwa_+!W1v7Fucq}{JR&wcYR1M|*{@6nHr$^G{Rw~J%G9(tHe$!J8i zsV7hpC=z$$C5arbk4z^Uof6uuzFaR8y^6hEG0q^KG>-HivErmGOtwuvqugy8UYUju*>!y4%@wLTd{oMJLG8XHV@vNY5YGKB$14QsJ6MINbq7bc3}%jgDEd6V0j) zPJcPD0sY~fVEuN7;${^lbt(DaWnNL^bZ~0LET8q1HizazVE)PRgpWcYSV_dU36c^& zOlV_FAx(&2G1aBY{c$cEfeVVZ&O=`$+2GL6ThBVLm`|B)pJuc)Uh{AYg`jmXbAPPy($Z6g7*{~^w*2cx%v1MdODL!Z|M$PYe9goe22VZ7Dw3Gu^ z_;v0xrd^|28xL=dpXuaCt!*o1H)K?*LM~Yu&Hhe)R>Q6B^xw`iHV?fV{`Kg84SitE zl#|vTuivhiON7S6I>w(U8K0$j$IGFwx1wA%C!61y2gg-{Iz2~{T=Yx7 zHJiCG-45g1JmfBi1=?I@O1M&Xq9&*TPuY(B)8lEodCN{W)>)*^Ph9KhoWVEg*H4z} z4Am8$#kdUIEeHE`l#v%E8v%1en2rxtZqtNgj~Z=by0KxI+Mu76ZP@;L!in6x2DbG; zo#1}0^a^nV#Q7G~aFhq#A*&Q^)z^#gh7_x9F*O*`I1N_nn`Jh4dvD=RXJOtRNP=UE z%6Dt>8lXQs+G;aodG%~l&(CSK?HnLZ7eL!JL$+B4 z{UTx~Qid}}KPsoT+FkdcNdQ!KdRbNG;=+opKl$S~>$f%8y~LvAYMj~H^Cq!{wD9!v z8aEM^%YJC>v{%Rbc_|=pZuEpfOsmEIOHts0c4<9#l-t}i&oM7Z4V;)4F-!w}s6)C{ zD?n|&u{syBplBjG?)(6Lo>?Gm?XI5PAPk7BG2(3x zszh}6y*)r^F5kL_egQ|;XqQ0h*K_h!W-}XY+gPo;mPOP_Zm4bwCzQ;892aS0fjqYb z%hYKil6RZsz5|y+&&;h?<&KT^rVPJldFNN6^xlZXwfp{(;lV%gCL~^g3)?7Yg4J=o zq0B%Mxcfj-85D)o_-p>UL~P&ONf@j-Y_NtwO`)4%#}Y<2ss-#Bb$`GP#FCw=7DLKU zJsQoR5kAV@Dg`~P82V>y4L3v@Aoowy=}5FM>3hx%C=>Bv(?#@kuJ8uUdilh<4X?)z zYNxW~Jmap;u9n(Gcm4R|6ce%Y_vW#9zugr*2M;UGAE>vP`)jQDT3Ea5lrO4pvMF&K zm-D)ec3bPiEDu5SQVJ+XGIOt9>573U+8`&GVHd}lkuDQO8ib%9E0B|2buL>QH*&3j z2$Z0OhcdzHw^sO1F z?jml(VwL*PdZo0aUk&we`r9X4J9*;KH@P4z>&1J~KED#SHiv*QnK&dUq4CK56; zO_H-cu)B4LN=kA9!<~BSNToFI&Q+;L^}?GGv(3 zO3Nb+^{&5G_Oxnka_}hwI+j|Ix_|_>W9s1wbJw)>w2e7q!f&OI{vBPPyHx;pJmwFa zqJTn;BgN}jj?Vel=-zIyTqVrP$)9<9Ai^EA|4!wpyXsy1DDW|Br6B1gSJg7KxKq2wJm2v-R5I)nZE^+1H zoH=|?T&PB$L@>DcAWBpU73g;$`JH6-O}3^N$7|cny(UbxV1_G=TM9O6t6us>o&f5y zr68M=0`gjur~%^|!Fxlvn>K!qeao-a0$NvUT#qGLoy5cGa-^=!ovlqzh*rYYD;=Zr z_89_OP$R@|A6zaS-*zU1K_+bX75C^2*(tu}))M(Xe%5D^JviBp6uB7{7b6)j8t4*q zt$&Q7c^O{Kc2@$^m?mR8jW;}8Y4B(x&=dhuV;Ei_3e=$=27`d=#01v!8_=RwXjpl> zS=UhgnL$+{wFgrZZAr@FI;Ya{-V3wjLg)gaZGJqQyb>Os@N|!_Cfe9=zIx{p=Pz@e z>B#cugv%w;wcJj_%M~>5#mTb%;@HV%G!aD8#=UbhP@i_k$Wb*=&RE&$Zhvvu$N3%__F`dP z%e7t^jtQBiZ3O=vdjYX`0~bDNZd2wdr>T06JOgEJ+tg`$^dCk{vO7$8XME+J9SM89 ze=0owX7}X(@Pjolv!iP@w4X!OZu|F9wGO)UGV=4HQ=T{~LTsI@H=`8Aw%_&uW$8sg ziMCe0i>E64!|#`#k0|uJ76(&s=5VYRufa8;-q9Zx_P$<5##Z9Ra;A(@oM-p_z3f^5 z@iiA$(P@^vOv6^y{;3m!VsTkl>fE1%4yP0l)8|JL$X3W~wHpwh{uS<3P#5gkXSa)c z7(RXOZtpSJG)JwxaJdUXe~I5PLB!U3f;?End+q5tTDsHr9TBQ`z{mo+>iAE=^0{Sbogh-*Mor{POlCv#s7JP+!?ugyxFY3@f; zn%U0h4n2JF)&DTUzs1`$n~lINRY2}b(Wx?B0083C)1{<*b5UU=bIiKmH}Zn{`XRKY zS)50V2X~2A6dg~>1&esC`V{g^Stw}=e zBF&u6*@U567swTtM?STm1Rs6J3K8WWr;>G1wpFE55Jg2WCG;{cwCMUy;(6-{`*w90 zWYksXQ7q1HQnHSzcO!Az+kZ^GbsJ&oHyoRV)dq?9I>k+(lz-o?9!jAH5}0tiH7FK-8I2Nw3Z2wnJ&0}2|v!k;nZYg z@wTqYIdAGHqZeFIKPdp3hEa9|pTs~uP%YsAO<_>q{CxMG!ZU!dfRuj){3t`iY{IZ|uTs^do3 zyS`|>u~(PH8?k^Xj|O+TcL%?(R~)sretY}ER&Tv5^$yObK1`FVa>loePmApaw_=C_ zohzVGThrqY8_DneAE80Z--HH1wckCs^IbSt^HSW`9sH;04Bi>TcMlIihc)ubWjd(P zNu7X9`E&2yVl%G8uD3WNvxY}qucQ}+<|bKDtc0qaq7+A5#t_s^cy#&2*~#uqVX*c-tcw$qNwEl%On>A*`DJ6# zf*1R?7iNXOOyE~Z3fDXCNnM88C6sA8?N!G7gnql&_THfy|dM6YbsCk|>W@xggbe=20o@>~r zc+8J7&?Eq97h?-f^$<`+mak13mC7~i)sd9mNmND{QH^$|#qr!OY!lxo{~ z9IcW{B8V`O0F4YSg)sRVc`tjVpCPU+x?)93B|N6x!65lfwZ0yws;tFEc{h(e zY%{!q0OI9GKpVrC5Qy;tEKaU0eQ1AiK@{x$W;xXI&eR}g(Fs(@c5N8JTvkw{pml^E z1##JQRrVgoNgpJ*;KuWxf*HNN&vtx`pI%|lZbTI5dbD@{!=VD^5AOu%KmM>pi(96C?qG|w=B&ITge@sX?Y%DxCb zO;UO1r2C~NJNRYmoA(y)y6w8rNRG5dj(R^+aRpH;H(#LF@_HhC>GHk^4^@yguRd4g zS4L~>R$d_uS=W-0r6u^FSX@s#iNG7SKukBFU3!l>**ar2$&W*XuTrL{8&3{@s^t2( zm-FC^XP}x2I0sFXSyHlMyFlp;XtUPBz8J?Jz1#U|pT zSr+`SqrK<<;|K1Y3pAGeEMcl0M~Ww(Xom>SbC%~kT?1e|N`}N^4`4Rzt(QfcoDF(~ z=zGS5Js+ikJ7tPWQ2-@@M6Q1R9ipw7JciKinkX>Zh?F3{$uyvnZ4=e~=_%x~-sP~l zzeY$P?7$mOoIlYKc!S*eZ*3=d?x1!9Y3ZqTQr@5Ee$5loTA08$o)!?PP6Gs_fJr?>t?Ge6PXumv_GTfBxa2dK(Cq0)>8HhRft%}f8*BaKU^Y0G62YmBn^*V~ z{guNFRZKtykC>4u49Sis@U0Z#f=hul2H%~_i#!~oWE)CzyVE-rgc(=2^+=p>Gcr+KxU9A zI_)kjcT-j`E|dscI(tt8&6TP72IyVHIxDbB2cjF9U|H7|bhLia_{fcg zIeU=M<)*D)q+2zDG#v_V3Jg$!CK$x`lOE45yS^q-5mH6!>K|?eN!YM8o5+L zVvqoy(O0Xf8zJy1FP#Hv#j0(f>xh<~K}w%Z%=7AhS^x3@CVEGCrW$PR?QT-PZwZUH s@jwlSxE!!9{ngg!JKz2H!`~zDdjx)u!0!?GJp#W+;QtkY!*9p`H{RNT?f?J) literal 0 HcmV?d00001 From 7db21263ad24168baf7a480eb0d8c4ab159fcdd8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 17:08:25 +1000 Subject: [PATCH 10/14] Add UnitConverter tests --- .../Common/Helpers/UnitConverter.cs | 23 ++++++++++- .../Helpers/UnitConverterHelperTests.cs | 41 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 9a7d25c55..c8b25bf56 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; @@ -19,6 +18,12 @@ namespace SixLabors.ImageSharp.Common.Helpers /// 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. @@ -57,6 +62,22 @@ namespace SixLabors.ImageSharp.Common.Helpers [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 . /// diff --git a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs new file mode 100644 index 000000000..57e280d93 --- /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); + } + } +} From fe9eaac426f3964a785d493cc018e25a5a88f0bb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 13 Jul 2018 21:20:23 +1000 Subject: [PATCH 11/14] Update xml docs. --- src/ImageSharp/MetaData/ImageMetaData.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index af5cc6191..40880bd08 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; @@ -102,6 +102,10 @@ 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; From 16422932b05f22cbd7631a17a49d58eaba9f93b2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 13 Jul 2018 21:20:54 +1000 Subject: [PATCH 12/14] See if we can fix appveyor by forcing redirects. --- src/ImageSharp/ImageSharp.csproj | 2 +- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index b40884f4b..6646feaa0 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -29,7 +29,7 @@ portable True IOperation - 7.3 + latest diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 9e15b6aba..31b81f2a2 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,13 +2,14 @@ net471;netcoreapp2.0;netcoreapp2.1;net47;net462 True - 7.2 + latest full portable True SixLabors.ImageSharp.Tests SixLabors.ImageSharp.Tests AnyCPU;x64;x86 + true true @@ -22,9 +23,6 @@ true - - - From 50e5a639a1a3dd134041dcebc0fcacddd2e210f8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 13 Jul 2018 21:27:45 +1000 Subject: [PATCH 13/14] Explicitly add reference. --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 31b81f2a2..7eeff2fdd 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -9,7 +9,6 @@ SixLabors.ImageSharp.Tests SixLabors.ImageSharp.Tests AnyCPU;x64;x86 - true true @@ -23,8 +22,8 @@ true - - + + From 9ed637dcb1fad263894b8563b0667e70520e0df1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 13 Jul 2018 21:39:53 +1000 Subject: [PATCH 14/14] Use previous VS environment. --- appveyor.yml | 2 +- src/ImageSharp/ImageSharp.csproj | 2 +- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3e6b79bfc..5d84e6cea 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/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6646feaa0..b40884f4b 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -29,7 +29,7 @@ portable True IOperation - latest + 7.3 diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 7eeff2fdd..9e15b6aba 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,7 +2,7 @@ net471;netcoreapp2.0;netcoreapp2.1;net47;net462 True - latest + 7.2 full portable True @@ -22,8 +22,11 @@ true - - + + + + +