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/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs
new file mode 100644
index 000000000..c8b25bf56
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.MetaData;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+
+namespace SixLabors.ImageSharp.Common.Helpers
+{
+ ///
+ /// Contains methods for converting values between unit scales.
+ ///
+ internal static class UnitConverter
+ {
+ ///
+ /// The number of centimeters in a meter.
+ /// 1 cm is equal to exactly 0.01 meters.
+ ///
+ private const double CmsInMeter = 1 / 0.01D;
+
+ ///
+ /// The number of centimeters in an inch.
+ /// 1 inch is equal to exactly 2.54 centimeters.
+ ///
+ private const double CmsInInch = 2.54D;
+
+ ///
+ /// The number of inches in a meter.
+ /// 1 inch is equal to exactly 0.0254 meters.
+ ///
+ private const double InchesInMeter = 1 / 0.0254D;
+
+ ///
+ /// Scales the value from centimeters to meters.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double CmToMeter(double x) => x * CmsInMeter;
+
+ ///
+ /// Scales the value from meters to centimeters.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double MeterToCm(double x) => x / CmsInMeter;
+
+ ///
+ /// Scales the value from meters to inches.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double MeterToInch(double x) => x / InchesInMeter;
+
+ ///
+ /// Scales the value from inches to meters.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double InchToMeter(double x) => x * InchesInMeter;
+
+ ///
+ /// Scales the value from centimeters to inches.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double CmToInch(double x) => x / CmsInInch;
+
+ ///
+ /// Scales the value from inches to centimeters.
+ ///
+ /// The value to scale.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static double InchToCm(double x) => x * CmsInInch;
+
+ ///
+ /// Converts an to a .
+ ///
+ /// The EXIF profile containing the value.
+ /// The
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile)
+ {
+ return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution)
+ ? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3
+ : default;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 20175613e..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;
@@ -59,6 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private Stream stream;
+ ///
+ /// The metadata
+ ///
+ private ImageMetaData metaData;
+
///
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
@@ -103,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
- var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
+ var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData);
Buffer2D pixels = image.GetRootFramePixelBuffer();
@@ -157,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public IImageInfo Identify(Stream stream)
{
this.ReadImageHeaders(stream, out _, out _);
- return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, new ImageMetaData());
+ return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metaData);
}
///
@@ -518,6 +524,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
}
+ // Resolution is stored in PPM.
+ var meta = new ImageMetaData();
+ meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
+ if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0)
+ {
+ meta.HorizontalResolution = this.infoHeader.XPelsPerMeter;
+ meta.VerticalResolution = this.infoHeader.YPelsPerMeter;
+ }
+ else
+ {
+ // Convert default metadata values to PPM.
+ meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultHorizontalResolution));
+ meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultVerticalResolution));
+ }
+
+ this.metaData = meta;
+
// skip the remaining header because we can't read those parts
this.stream.Skip(skipAmount);
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 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/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index fc73f55a1..462f09897 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 = PixelResolutionUnit.AspectRatio;
+ float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F;
+
+ if (ratio > 1)
+ {
+ meta.HorizontalResolution = ratio;
+ meta.VerticalResolution = 1;
+ }
+ else
+ {
+ meta.VerticalResolution = 1 / ratio;
+ meta.HorizontalResolution = 1;
+ }
+ }
+
+ this.metaData = meta;
+
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index e4737f3bc..ea507c781 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -226,11 +226,41 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1);
+ // The Pixel Aspect Ratio is defined to be the quotient of the pixel's
+ // width over its height. The value range in this field allows
+ // specification of the widest pixel of 4:1 to the tallest pixel of
+ // 1:4 in increments of 1/64th.
+ //
+ // Values : 0 - No aspect ratio information is given.
+ // 1..255 - Value used in the computation.
+ //
+ // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
+ ImageMetaData meta = image.MetaData;
+ byte ratio = 0;
+
+ if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio)
+ {
+ double hr = meta.HorizontalResolution;
+ double vr = meta.VerticalResolution;
+ if (hr != vr)
+ {
+ if (hr > vr)
+ {
+ ratio = (byte)((hr * 64) - 15);
+ }
+ else
+ {
+ ratio = (byte)(((1 / vr) * 64) - 15);
+ }
+ }
+ }
+
var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width,
height: (ushort)image.Height,
packed: packedValue,
- backgroundColorIndex: unchecked((byte)transparencyIndex));
+ backgroundColorIndex: unchecked((byte)transparencyIndex),
+ ratio);
descriptor.WriteTo(this.buffer);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
index 591af6344..f153ce062 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using SixLabors.ImageSharp.MetaData;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
@@ -28,10 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Guard.MustBeGreaterThan(xDensity, 0, nameof(xDensity));
Guard.MustBeGreaterThan(yDensity, 0, nameof(yDensity));
+ Guard.MustBeBetweenOrEqualTo(densityUnits, 0, 2, nameof(densityUnits));
this.MajorVersion = majorVersion;
this.MinorVersion = minorVersion;
- this.DensityUnits = densityUnits;
+ this.DensityUnits = (PixelResolutionUnit)densityUnits;
this.XDensity = xDensity;
this.YDensity = yDensity;
}
@@ -52,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// 01 : Pixels per inch (2.54 cm)
/// 02 : Pixels per centimeter
///
- public byte DensityUnits { get; }
+ public PixelResolutionUnit DensityUnits { get; }
///
/// Gets the horizontal pixel density. Must not be zero.
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 1310d90d2..ada33f2b8 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
-
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
+using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@@ -210,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int componentCount = 3;
// Write the Start Of Image marker.
- this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution);
+ this.WriteApplicationHeader(image.MetaData);
this.WriteProfiles(image);
@@ -425,9 +427,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Writes the application header containing the JFIF identifier plus extra data.
///
- /// The resolution of the image in the x- direction.
- /// The resolution of the image in the y- direction.
- private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
+ /// The image meta data.
+ private void WriteApplicationHeader(ImageMetaData meta)
{
// Write the start of image marker. Markers are always prefixed with with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
@@ -445,13 +446,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[10] = 0x00; // = "JFIF",'\0'
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
- this.buffer[13] = 0x01; // xyunits as dpi
// Resolution. Big Endian
- this.buffer[14] = (byte)(horizontalResolution >> 8);
- this.buffer[15] = (byte)horizontalResolution;
- this.buffer[16] = (byte)(verticalResolution >> 8);
- this.buffer[17] = (byte)verticalResolution;
+ Span hResolution = this.buffer.AsSpan(14, 2);
+ Span vResolution = this.buffer.AsSpan(16, 2);
+
+ if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
+ {
+ // Scale down to PPI
+ this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
+ BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
+ BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
+ }
+ else
+ {
+ // We can simply pass the value.
+ this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits
+ BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
+ BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
+ }
// No thumbnail
this.buffer[18] = 0x00; // Thumbnail width
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
index a360d5477..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;
@@ -412,6 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
+ this.MetaData.ResolutionUnits = this.jFif.DensityUnits;
}
else if (this.isExif)
{
@@ -427,6 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
this.MetaData.HorizontalResolution = horizontalValue;
this.MetaData.VerticalResolution = verticalValue;
+ this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile);
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 04d4f057c..38c550344 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -10,6 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.MetaData;
@@ -233,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.Array);
+ this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image == null)
@@ -307,7 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.Array);
+ this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
@@ -396,9 +397,26 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The data containing physical data.
private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan data)
{
- // 39.3700787 = inches in a meter.
- metadata.HorizontalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)) / 39.3700787d;
- metadata.VerticalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)) / 39.3700787d;
+ // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
+ // Pixels per unit, X axis: 4 bytes (unsigned integer)
+ // Pixels per unit, Y axis: 4 bytes (unsigned integer)
+ // Unit specifier: 1 byte
+ //
+ // The following values are legal for the unit specifier:
+ // 0: unit is unknown
+ // 1: unit is the meter
+ //
+ // When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
+ int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4));
+ int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4));
+ byte unit = data[8];
+
+ metadata.ResolutionUnits = unit == byte.MinValue
+ ? PixelResolutionUnit.AspectRatio
+ : PixelResolutionUnit.PixelsPerMeter;
+
+ metadata.HorizontalResolution = hResolution;
+ metadata.VerticalResolution = vResolution;
}
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 69f04979c..df0dffa49 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -6,8 +6,10 @@ using System.Buffers.Binary;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
+using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.Memory;
@@ -598,19 +600,52 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WritePhysicalChunk(Stream stream, Image image)
where TPixel : struct, IPixel
{
- if (image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0)
+ // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
+ // Pixels per unit, X axis: 4 bytes (unsigned integer)
+ // Pixels per unit, Y axis: 4 bytes (unsigned integer)
+ // Unit specifier: 1 byte
+ //
+ // The following values are legal for the unit specifier:
+ // 0: unit is unknown
+ // 1: unit is the meter
+ //
+ // When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
+ ImageMetaData meta = image.MetaData;
+ Span hResolution = this.chunkDataBuffer.AsSpan(0, 4);
+ Span vResolution = this.chunkDataBuffer.AsSpan(4, 4);
+
+ switch (meta.ResolutionUnits)
{
- // 39.3700787 = inches in a meter.
- int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D);
- int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D);
+ case PixelResolutionUnit.AspectRatio:
- BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), dpmX);
- BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), dpmY);
+ this.chunkDataBuffer[8] = 0;
+ BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
+ BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
+ break;
+
+ case PixelResolutionUnit.PixelsPerInch:
+
+ this.chunkDataBuffer[8] = 1; // Per meter
+ BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)));
+ BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)));
+ break;
+
+ case PixelResolutionUnit.PixelsPerCentimeter:
- this.chunkDataBuffer[8] = 1;
+ this.chunkDataBuffer[8] = 1; // Per meter
+ BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)));
+ BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)));
+ break;
+
+ default:
- this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
+ this.chunkDataBuffer[8] = 1; // Per meter
+ BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
+ BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
+ break;
}
+
+ this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
///
diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs
index af3cc5f5f..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;
@@ -47,6 +47,7 @@ namespace SixLabors.ImageSharp.MetaData
{
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
+ this.ResolutionUnits = other.ResolutionUnits;
this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties)
@@ -99,6 +100,15 @@ namespace SixLabors.ImageSharp.MetaData
}
}
+ ///
+ /// Gets or sets unit of measure used when reporting resolution.
+ /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
+ /// 01 : Pixels per inch (2.54 cm)
+ /// 02 : Pixels per centimeter
+ /// 03 : Pixels per meter
+ ///
+ public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch;
+
///
/// Gets or sets the Exif profile.
///
diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs
new file mode 100644
index 000000000..ce848004f
--- /dev/null
+++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.MetaData
+{
+ ///
+ /// Provides enumeration of available pixel density units.
+ ///
+ public enum PixelResolutionUnit : byte
+ {
+ ///
+ /// No units; width:height pixel aspect ratio.
+ ///
+ AspectRatio = 0,
+
+ ///
+ /// Pixels per inch (2.54 cm).
+ ///
+ PixelsPerInch = 1,
+
+ ///
+ /// Pixels per centimeter.
+ ///
+ PixelsPerCentimeter = 2,
+
+ ///
+ /// Pixels per meter (100 cm).
+ ///
+ PixelsPerMeter = 3
+ }
+}
diff --git a/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/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index ceb60ae5c..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;
@@ -13,7 +12,7 @@ using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using System.Collections.Generic;
-
+ using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
public class GifDecoderTests
@@ -21,31 +20,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;
public static readonly string[] MultiFrameTestFiles =
- {
- TestImages.Gif.Giphy, TestImages.Gif.Kumin
- };
+ {
+ TestImages.Gif.Giphy, TestImages.Gif.Kumin
+ };
public static readonly string[] BasicVerificationFiles =
- {
- TestImages.Gif.Cheers,
- TestImages.Gif.Rings,
+ {
+ TestImages.Gif.Cheers,
+ TestImages.Gif.Rings,
- // previously DecodeBadApplicationExtensionLength:
- TestImages.Gif.Issues.BadAppExtLength,
- TestImages.Gif.Issues.BadAppExtLength_2,
+ // previously DecodeBadApplicationExtensionLength:
+ TestImages.Gif.Issues.BadAppExtLength,
+ TestImages.Gif.Issues.BadAppExtLength_2,
- // previously DecodeBadDescriptorDimensionsLength:
- TestImages.Gif.Issues.BadDescriptorWidth
- };
+ // previously DecodeBadDescriptorDimensionsLength:
+ TestImages.Gif.Issues.BadDescriptorWidth
+ };
+
+ public static readonly TheoryData RatioFiles =
+ new TheoryData
+ {
+ { TestImages.Gif.Rings, (int)ImageMetaData.DefaultHorizontalResolution, (int)ImageMetaData.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch},
+ { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
+ { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
+ };
private static readonly Dictionary BasicVerificationFrameCount =
- new Dictionary
- {
- [TestImages.Gif.Cheers] = 93,
- [TestImages.Gif.Issues.BadDescriptorWidth] = 36,
- };
+ new Dictionary
+ {
+ [TestImages.Gif.Cheers] = 93,
+ [TestImages.Gif.Issues.BadDescriptorWidth] = 36,
+ };
- public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 };
+ public static readonly string[] BadAppExtFiles =
+ {
+ TestImages.Gif.Issues.BadAppExtLength,
+ TestImages.Gif.Issues.BadAppExtLength_2
+ };
[Theory]
[WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)]
@@ -59,6 +70,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
}
}
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new GifDecoder();
+ using (Image image = decoder.Decode(Configuration.Default, stream))
+ {
+ ImageMetaData meta = image.MetaData;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new GifDecoder();
+ IImageInfo image = decoder.Identify(Configuration.Default, stream);
+ ImageMetaData meta = image.MetaData;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
+
[Theory]
[WithFile(TestImages.Gif.Trans, TestPixelTypes)]
public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider)
@@ -88,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
}
-
+
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
@@ -169,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[InlineData(TestImages.Gif.Trans, 8)]
public void DetectPixelSize(string imagePath, int expectedPixelSize)
{
- TestFile testFile = TestFile.Create(imagePath);
+ var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index 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/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs
index 332899e8d..b2dc3534d 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
-
+using SixLabors.ImageSharp.MetaData;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
@@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.True(isJFif);
Assert.Equal(1, marker.MajorVersion);
Assert.Equal(1, marker.MinorVersion);
- Assert.Equal(1, marker.DensityUnits);
+ Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits);
Assert.Equal(96, marker.XDensity);
Assert.Equal(96, marker.YDensity);
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
index 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.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)
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 66e4f39fd..54f3e397c 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -8,6 +8,7 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -18,8 +19,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public partial class PngDecoderTests
{
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32;
-
-
public static readonly string[] CommonTestImages =
{
@@ -67,6 +66,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
TestImages.Png.GrayTrns16BitInterlaced
};
+ public static readonly TheoryData RatioFiles =
+ new TheoryData
+ {
+ { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter},
+ { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
+ { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
+ };
+
[Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
public void Decode(TestImageProvider provider)
@@ -218,5 +225,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
}
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new PngDecoder();
+ using (Image image = decoder.Decode(Configuration.Default, stream))
+ {
+ ImageMetaData meta = image.MetaData;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new PngDecoder();
+ IImageInfo image = decoder.Identify(Configuration.Default, stream);
+ ImageMetaData meta = image.MetaData;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 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/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);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index b0bdad8e5..142b923ed 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -65,6 +65,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png";
public const string Banner8Index = "Png/banner8-index.png";
+ public const string Ratio1x4 = "Png/ratio-1x4.png";
+ public const string Ratio4x1 = "Png/ratio-4x1.png";
+
public static class Bad
{
// Odd chunk lengths
@@ -79,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests
Powerpoint, SplashInterlaced, Interlaced,
Filter0, Filter1, Filter2, Filter3, Filter4,
FilterVar, VimImage1, VimImage2, VersioningImage1,
- VersioningImage2
+ VersioningImage2, Ratio4x1, Ratio1x4
};
}
@@ -124,13 +127,14 @@ namespace SixLabors.ImageSharp.Tests
public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg";
public const string Testorig420 = "Jpg/baseline/testorig.jpg";
public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
+ public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
public static readonly string[] All =
- {
- Cmyk, Ycck, Exif, Floorplan,
- Calliphora, Turtle, GammaDalaiLamaGray,
- Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444,
- };
+ {
+ Cmyk, Ycck, Exif, Floorplan,
+ Calliphora, Turtle, GammaDalaiLamaGray,
+ Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1
+ };
}
public static class Issues
@@ -176,15 +180,17 @@ namespace SixLabors.ImageSharp.Tests
public const string Cheers = "Gif/cheers.gif";
public const string Trans = "Gif/trans.gif";
public const string Kumin = "Gif/kumin.gif";
+ public const string Ratio4x1 = "Gif/base_4x1.gif";
+ public const string Ratio1x4 = "Gif/base_1x4.gif";
- public class Issues
+ public static class Issues
{
public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif";
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
}
- public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin };
+ public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Ratio4x1, Ratio1x4 };
}
}
}
diff --git a/tests/Images/Input/Gif/base_1x4.gif b/tests/Images/Input/Gif/base_1x4.gif
new file mode 100644
index 000000000..b4b89b525
--- /dev/null
+++ b/tests/Images/Input/Gif/base_1x4.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:56e2409223f140145db2ba405f5562451dc0fa9d4274830fb02bd78d42552162
+size 1620
diff --git a/tests/Images/Input/Gif/base_4x1.gif b/tests/Images/Input/Gif/base_4x1.gif
new file mode 100644
index 000000000..31d2c07e8
--- /dev/null
+++ b/tests/Images/Input/Gif/base_4x1.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:24fd7e9dd3c6516ddab7336a30efc5901754b6d43a9d989c6fbb3e06d8944c80
+size 1620
diff --git a/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg
new file mode 100644
index 000000000..e5d987e8c
--- /dev/null
+++ b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1e9410c16bc61f08bbc18ae0e0b13181c9c4f57c66e82a7c6e593a57f40756d7
+size 34674
diff --git a/tests/Images/Input/Png/ratio-1x4.png b/tests/Images/Input/Png/ratio-1x4.png
new file mode 100644
index 000000000..37bbb27e6
--- /dev/null
+++ b/tests/Images/Input/Png/ratio-1x4.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:438018f19c85e582cb586ac7cca2220008ecb7fc70ce50e8f2a76b494c128a20
+size 404
diff --git a/tests/Images/Input/Png/ratio-4x1.png b/tests/Images/Input/Png/ratio-4x1.png
new file mode 100644
index 000000000..b494a47d1
--- /dev/null
+++ b/tests/Images/Input/Png/ratio-4x1.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a068eaf1f7040490e08eda3259befb6689849dd0ff8bb4cc03c705d117cb2b9f
+size 344