diff --git a/src/ImageSharp/Formats/Png/Chunks/PngPhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PngPhysicalChunkData.cs
new file mode 100644
index 0000000000..39a9676b63
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Chunks/PngPhysicalChunkData.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Buffers.Binary;
+using SixLabors.ImageSharp.Common.Helpers;
+using SixLabors.ImageSharp.MetaData;
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
+ ///
+ internal readonly struct PngPhysicalChunkData
+ {
+ public const int Size = 9;
+
+ public PngPhysicalChunkData(uint x, uint y, byte unitSpecifier)
+ {
+ this.XAxisPixelsPerUnit = x;
+ this.YAxisPixelsPerUnit = y;
+ this.UnitSpecifier = unitSpecifier;
+ }
+
+ ///
+ /// Gets the number of pixels per unit on the X axis.
+ ///
+ public uint XAxisPixelsPerUnit { get; }
+
+ ///
+ /// Gets the number of pixels per unit on the Y axis.
+ ///
+ public uint YAxisPixelsPerUnit { get; }
+
+ ///
+ /// Gets 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.
+ ///
+ public byte UnitSpecifier { get; }
+
+ ///
+ /// Constructs the PngPhysicalChunkData from the provided metadata.
+ /// If the resolution units are not in meters, they are automatically convereted.
+ ///
+ /// The metadata.
+ /// The constructed PngPhysicalChunkData instance.
+ public static PngPhysicalChunkData FromMetadata(ImageMetaData meta)
+ {
+ byte unitSpecifier = 0;
+ uint x;
+ uint y;
+
+ switch (meta.ResolutionUnits)
+ {
+ case PixelResolutionUnit.AspectRatio:
+ unitSpecifier = 0; // Unspecified
+ x = (uint)Math.Round(meta.HorizontalResolution);
+ y = (uint)Math.Round(meta.VerticalResolution);
+ break;
+
+ case PixelResolutionUnit.PixelsPerInch:
+ unitSpecifier = 1; // Per meter
+ x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution));
+ y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution));
+ break;
+
+ case PixelResolutionUnit.PixelsPerCentimeter:
+ unitSpecifier = 1; // Per meter
+ x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution));
+ y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution));
+ break;
+
+ default:
+ unitSpecifier = 1; // Per meter
+ x = (uint)Math.Round(meta.HorizontalResolution);
+ y = (uint)Math.Round(meta.VerticalResolution);
+ break;
+ }
+
+ return new PngPhysicalChunkData(x, y, unitSpecifier);
+ }
+
+ ///
+ /// Writes the data to the given buffer.
+ ///
+ /// The buffer.
+ public void WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(0, 4), this.XAxisPixelsPerUnit);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit);
+ buffer[8] = this.UnitSpecifier;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 525cc8bd1c..7f3c9945ad 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -674,47 +674,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The image meta data.
private void WritePhysicalChunk(Stream stream, ImageMetaData meta)
{
- // 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.
- Span hResolution = this.chunkDataBuffer.AsSpan(0, 4);
- Span vResolution = this.chunkDataBuffer.AsSpan(4, 4);
-
- switch (meta.ResolutionUnits)
- {
- case PixelResolutionUnit.AspectRatio:
- 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; // 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; // Per meter
- BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
- BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
- break;
- }
+ PngPhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
- this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
+ this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PngPhysicalChunkData.Size);
}
///