diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
index be2af7a3a7..8e2db4e8e9 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 ff25e26b7a..df0e16a17b 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 04d4f057ce..f95b9970ad 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 69f04979cf..6aef2a4d9e 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);
}
///