From 965bc1037debd8d662a4657eb1913a7d257fbb2f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Jul 2018 00:02:30 +1000 Subject: [PATCH] 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 } }