From 95e77e3ba3034596abb2eac7a1bcca852ffb8a10 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 7 Jul 2018 18:03:14 +1000 Subject: [PATCH] Use UnitConverter to abstract complexity. --- .../Common/Helpers/UnitConverter.cs | 73 +++++++++++++++++++ src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../Jpeg/Components/Decoder/JFifMarker.cs | 5 +- .../Formats/Jpeg/JpegEncoderCore.cs | 23 +++--- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 8 +- src/ImageSharp/Formats/Png/PngConstants.cs | 6 -- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 ++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 22 +++--- src/ImageSharp/MetaData/ImageMetaData.cs | 2 +- ...olutionUnits.cs => PixelResolutionUnit.cs} | 2 +- .../Formats/Jpg/JFifMarkerTests.cs | 2 +- 12 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/UnitConverter.cs rename src/ImageSharp/MetaData/{ResolutionUnits.cs => PixelResolutionUnit.cs} (93%) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs new file mode 100644 index 0000000000..9a7d25c559 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 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; + + /// + /// 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/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 553748f955..462f09897c 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -535,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 if (this.logicalScreenDescriptor.PixelAspectRatio > 0) { - meta.ResolutionUnits = ResolutionUnits.AspectRatio; + meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; if (ratio > 1) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index f5c6ac3531..ea507c7811 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Gif ImageMetaData meta = image.MetaData; byte ratio = 0; - if (meta.ResolutionUnits == ResolutionUnits.AspectRatio) + if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio) { double hr = meta.HorizontalResolution; double vr = meta.VerticalResolution; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index af0ceea10a..f153ce062a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -29,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 = (ResolutionUnits)densityUnits; + this.DensityUnits = (PixelResolutionUnit)densityUnits; this.XDensity = xDensity; this.YDensity = yDensity; } @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 01 : Pixels per inch (2.54 cm) /// 02 : Pixels per centimeter /// - public ResolutionUnits 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 aee5fed8f4..80f21c65df 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.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,10 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader( - (byte)image.MetaData.ResolutionUnits, - (short)image.MetaData.HorizontalResolution, - (short)image.MetaData.VerticalResolution); + this.WriteApplicationHeader(image.MetaData); this.WriteProfiles(image); @@ -428,10 +427,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the application header containing the JFIF identifier plus extra data. /// - /// The resolution unit of measurement. - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. - private void WriteApplicationHeader(byte resolutionUnits, 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; @@ -449,13 +446,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[11] = 0x01; // versionhi this.buffer[12] = 0x01; // versionlo - this.buffer[13] = resolutionUnits; // xyunits // 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; + 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)); // 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 8e2db4e8e9..409908eac0 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; @@ -424,15 +424,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ? ((Rational)verticalTag.Value).ToDouble() : 0; - byte units = this.MetaData.ExifProfile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolutionTag) - ? (byte)(((ushort)resolutionTag.Value) - 1) // ExifTag.ResolutionUnit values are 1, 2, 3 - : byte.MinValue; - if (horizontalValue > 0 && verticalValue > 0) { this.MetaData.HorizontalResolution = horizontalValue; this.MetaData.VerticalResolution = verticalValue; - this.MetaData.ResolutionUnits = (ResolutionUnits)units; + this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index df0e16a17b..ff25e26b7a 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -41,11 +41,5 @@ 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 f95b9970ad..fd2c636bbc 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; @@ -406,7 +407,6 @@ namespace SixLabors.ImageSharp.Formats.Png // 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]; @@ -415,15 +415,14 @@ namespace SixLabors.ImageSharp.Formats.Png { metadata.HorizontalResolution = hResolution; metadata.VerticalResolution = vResolution; - metadata.ResolutionUnits = ResolutionUnits.AspectRatio; + metadata.ResolutionUnits = PixelResolutionUnit.AspectRatio; return; } - // Use PPI for its commonality. - const double inchesInMeter = PngConstants.InchesInMeter; - metadata.HorizontalResolution = hResolution / inchesInMeter; - metadata.VerticalResolution = vResolution / inchesInMeter; - metadata.ResolutionUnits = ResolutionUnits.PixelsPerInch; + // Use PPC since original is in meters. + metadata.HorizontalResolution = UnitConverter.MeterToCm(hResolution); + metadata.VerticalResolution = UnitConverter.MeterToCm(vResolution); + metadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 6aef2a4d9e..b8a5c22bbc 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -6,6 +6,7 @@ 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; @@ -609,32 +610,31 @@ namespace SixLabors.ImageSharp.Formats.Png // 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) { - case ResolutionUnits.AspectRatio: + case PixelResolutionUnit.AspectRatio: this.chunkDataBuffer[8] = 0; BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); break; - case ResolutionUnits.PixelsPerCentimeter: + case PixelResolutionUnit.PixelsPerCentimeter: - 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)); + 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; - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.HorizontalResolution * PngConstants.InchesInMeter)); + 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; } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 9a7e2d179e..af5cc61915 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Gets or sets unit of measure used when reporting resolution. /// - public ResolutionUnits ResolutionUnits { get; set; } = ResolutionUnits.PixelsPerInch; + public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; /// /// Gets or sets the Exif profile. diff --git a/src/ImageSharp/MetaData/ResolutionUnits.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs similarity index 93% rename from src/ImageSharp/MetaData/ResolutionUnits.cs rename to src/ImageSharp/MetaData/PixelResolutionUnit.cs index 7dfd62de83..899dedeb82 100644 --- a/src/ImageSharp/MetaData/ResolutionUnits.cs +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Provides enumeration of available pixel density units. /// - public enum ResolutionUnits : byte + public enum PixelResolutionUnit : byte { /// /// No units; width:height pixel aspect ratio = Ydensity:Xdensity diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index e7d8845b4a..b2dc3534d1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -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(ResolutionUnits.PixelsPerInch, marker.DensityUnits); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); Assert.Equal(96, marker.XDensity); Assert.Equal(96, marker.YDensity); }