From 9210d4cc3562f4529bc6545658f6690cf06f1d91 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Jul 2018 22:07:44 +1000 Subject: [PATCH] Can now preserve correct resolution for jpeg and gif. --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 36 ++++++++++++++++--- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 32 ++++++++++++++++- .../Jpeg/Components/Decoder/JFifMarker.cs | 5 +-- .../Formats/Jpeg/JpegEncoderCore.cs | 10 ++++-- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 6 ++++ src/ImageSharp/MetaData/ImageMetaData.cs | 6 ++++ .../ResolutionUnits.cs} | 4 +-- .../Formats/Jpg/JFifMarkerTests.cs | 4 +-- 8 files changed, 89 insertions(+), 14 deletions(-) rename src/ImageSharp/{Formats/DensityUnits.cs => MetaData/ResolutionUnits.cs} (88%) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index fc73f55a1e..553748f955 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -450,8 +450,8 @@ namespace SixLabors.ImageSharp.Formats.Gif { int index = Unsafe.Add(ref indicesRef, i); - if (this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) + if (!this.graphicsControlExtension.TransparencyFlag + || this.graphicsControlExtension.TransparencyIndex != index) { ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); rgba.Rgb = colorTable[index]; @@ -516,14 +516,42 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The stream containing image data. private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) { - this.metaData = new ImageMetaData(); - this.stream = stream; // Skip the identifier this.stream.Skip(6); this.ReadLogicalScreenDescriptor(); + var meta = new ImageMetaData(); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + if (this.logicalScreenDescriptor.PixelAspectRatio > 0) + { + meta.ResolutionUnits = ResolutionUnits.AspectRatio; + float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; + + if (ratio > 1) + { + meta.HorizontalResolution = ratio; + meta.VerticalResolution = 1; + } + else + { + meta.VerticalResolution = 1 / ratio; + meta.HorizontalResolution = 1; + } + } + + this.metaData = meta; + if (this.logicalScreenDescriptor.GlobalColorTableFlag) { int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index e4737f3bc5..f5c6ac3531 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -226,11 +226,41 @@ namespace SixLabors.ImageSharp.Formats.Gif { byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1); + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + ImageMetaData meta = image.MetaData; + byte ratio = 0; + + if (meta.ResolutionUnits == ResolutionUnits.AspectRatio) + { + double hr = meta.HorizontalResolution; + double vr = meta.VerticalResolution; + if (hr != vr) + { + if (hr > vr) + { + ratio = (byte)((hr * 64) - 15); + } + else + { + ratio = (byte)(((1 / vr) * 64) - 15); + } + } + } + var descriptor = new GifLogicalScreenDescriptor( width: (ushort)image.Width, height: (ushort)image.Height, packed: packedValue, - backgroundColorIndex: unchecked((byte)transparencyIndex)); + backgroundColorIndex: unchecked((byte)transparencyIndex), + ratio); descriptor.WriteTo(this.buffer); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 591af63442..af0ceea10a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.MetaData; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.MajorVersion = majorVersion; this.MinorVersion = minorVersion; - this.DensityUnits = densityUnits; + this.DensityUnits = (ResolutionUnits)densityUnits; this.XDensity = xDensity; this.YDensity = yDensity; } @@ -52,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 01 : Pixels per inch (2.54 cm) /// 02 : Pixels per centimeter /// - public byte DensityUnits { get; } + public ResolutionUnits 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 1310d90d26..aee5fed8f4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -210,7 +210,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution); + this.WriteApplicationHeader( + (byte)image.MetaData.ResolutionUnits, + (short)image.MetaData.HorizontalResolution, + (short)image.MetaData.VerticalResolution); this.WriteProfiles(image); @@ -425,9 +428,10 @@ 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(short horizontalResolution, short verticalResolution) + private void WriteApplicationHeader(byte resolutionUnits, short horizontalResolution, short verticalResolution) { // Write the start of image marker. Markers are always prefixed with with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; @@ -445,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[11] = 0x01; // versionhi this.buffer[12] = 0x01; // versionlo - this.buffer[13] = 0x01; // xyunits as dpi + this.buffer[13] = resolutionUnits; // xyunits // Resolution. Big Endian this.buffer[14] = (byte)(horizontalResolution >> 8); diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index a360d54771..be2af7a3a7 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -412,6 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { this.MetaData.HorizontalResolution = this.jFif.XDensity; this.MetaData.VerticalResolution = this.jFif.YDensity; + this.MetaData.ResolutionUnits = this.jFif.DensityUnits; } else if (this.isExif) { @@ -423,10 +424,15 @@ 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) // EXIF is 1,2,3 + : byte.MinValue; + if (horizontalValue > 0 && verticalValue > 0) { this.MetaData.HorizontalResolution = horizontalValue; this.MetaData.VerticalResolution = verticalValue; + this.MetaData.ResolutionUnits = (ResolutionUnits)units; } } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index af3cc5f5fd..9a7e2d179e 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -47,6 +47,7 @@ namespace SixLabors.ImageSharp.MetaData { this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; + this.ResolutionUnits = other.ResolutionUnits; this.RepeatCount = other.RepeatCount; foreach (ImageProperty property in other.Properties) @@ -99,6 +100,11 @@ namespace SixLabors.ImageSharp.MetaData } } + /// + /// Gets or sets unit of measure used when reporting resolution. + /// + public ResolutionUnits ResolutionUnits { get; set; } = ResolutionUnits.PixelsPerInch; + /// /// Gets or sets the Exif profile. /// diff --git a/src/ImageSharp/Formats/DensityUnits.cs b/src/ImageSharp/MetaData/ResolutionUnits.cs similarity index 88% rename from src/ImageSharp/Formats/DensityUnits.cs rename to src/ImageSharp/MetaData/ResolutionUnits.cs index ce30bd06cd..7dfd62de83 100644 --- a/src/ImageSharp/Formats/DensityUnits.cs +++ b/src/ImageSharp/MetaData/ResolutionUnits.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.MetaData { /// /// Provides enumeration of available pixel density units. /// - public enum DensityUnits : byte + public enum ResolutionUnits : 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 332899e8df..e7d8845b4a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - +using SixLabors.ImageSharp.MetaData; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg @@ -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(1, marker.DensityUnits); + Assert.Equal(ResolutionUnits.PixelsPerInch, marker.DensityUnits); Assert.Equal(96, marker.XDensity); Assert.Equal(96, marker.YDensity); }