Browse Source

Properly handle png resolution.

af/merge-core
James Jackson-South 8 years ago
parent
commit
f8256e3d8d
  1. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  2. 6
      src/ImageSharp/Formats/Png/PngConstants.cs
  3. 35
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  4. 44
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

2
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)

6
src/ImageSharp/Formats/Png/PngConstants.cs

@ -41,5 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The header bytes as a big endian coded ulong.
/// </summary>
public const ulong HeaderValue = 0x89504E470D0A1A0AUL;
/// <summary>
/// The number of inches in a meter. Used for converting PPM to PPI in <see cref="PngChunkType.Physical"/>.
/// One inch is equal to exactly 0.0254 meters.
/// </summary>
public const double InchesInMeter = 1 / 0.0254D;
}
}

35
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
/// <param name="data">The data containing physical data.</param>
private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan<byte> 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;
}
/// <summary>

44
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<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
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<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4);
Span<byte> 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);
}
/// <summary>

Loading…
Cancel
Save