diff --git a/CI/job_templates/test_drawing_libraries.yml b/CI/job_templates/test_drawing_libraries.yml index ac8c71e..d6a6b42 100644 --- a/CI/job_templates/test_drawing_libraries.yml +++ b/CI/job_templates/test_drawing_libraries.yml @@ -20,6 +20,14 @@ jobs: buildType: 'current' artifactName: 'IronDrawingDataTests' targetPath: '$(Agent.BuildDirectory)/Data' + - ${{ if or(eq(parameters.OSPlatform, 'Ubuntu'), eq(parameters.OSPlatform, 'Linux')) }}: + - task: Bash@3 + displayName: 'Install GDI+ dependencies' + inputs: + targetType: 'inline' + script: | + sudo apt-get update + sudo apt-get install -y libgdiplus libc6-dev - ${{ if eq(parameters.framework, 'netcoreapp3.1') }}: - task: UseDotNet@2 displayName: 'Install .Netcoreapp3.1 Core sdk' diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap300Input.bmp b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap300Input.bmp new file mode 100644 index 0000000..0397ef3 Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap300Input.bmp differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap96Input.bmp b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap96Input.bmp new file mode 100644 index 0000000..0f02fce Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Bitmap96Input.bmp differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg300Input.jpg b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg300Input.jpg new file mode 100644 index 0000000..742d5ff Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg300Input.jpg differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg72Input.jpg b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg72Input.jpg new file mode 100644 index 0000000..369c40c Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Jpg72Input.jpg differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif120Input.tiff b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif120Input.tiff new file mode 100644 index 0000000..e87e9c6 Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif120Input.tiff differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif200Input.tif b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif200Input.tif new file mode 100644 index 0000000..7b3a37e Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 MultiPageTif200Input.tif differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png300Input.png b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png300Input.png new file mode 100644 index 0000000..a1b2eef Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png300Input.png differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png96Input.png b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png96Input.png new file mode 100644 index 0000000..5098a69 Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 Png96Input.png differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif300Input.tif b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif300Input.tif new file mode 100644 index 0000000..51ea246 Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif300Input.tif differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif72Input.tiff b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif72Input.tiff new file mode 100644 index 0000000..8aaf78c Binary files /dev/null and b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/Data/DW-26 SinglePageTif72Input.tiff differ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs index 76c4a65..83f30a5 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs @@ -807,6 +807,28 @@ public void TestLoadAnyBitmapFromRGBBuffer() } } + [TheoryWithAutomaticDisplayName()] + [InlineData("DW-26 Bitmap96Input.bmp", 96, 96)] + [InlineData("DW-26 Bitmap300Input.bmp", 300, 300)] + [InlineData("DW-26 Jpg300Input.jpg", 300, 300)] + [InlineData("DW-26 Jpg72Input.jpg", 72, 72)] + [InlineData("DW-26 Png300Input.png", 300, 300)] + [InlineData("DW-26 Png96Input.png", 96,96)] + [InlineData("DW-26 SinglePageTif72Input.tiff", 72, 72)] + [InlineData("DW-26 SinglePageTif300Input.tif", 300, 300)] + [InlineData("DW-26 MultiPageTif120Input.tiff", 120, 120)] + [InlineData("DW-26 MultiPageTif200Input.tif", 200, 200)] + public void AnyBitmapShouldReturnCorrectResolutions(string fileName, double expectedHorizontalResolution, double expectedVerticalResolution) + { + string imagePath = GetRelativeFilePath(fileName); + var bitmap = AnyBitmap.FromFile(imagePath); + for (int i = 0; i < bitmap.FrameCount; i++) + { + Assert.Equal(expectedHorizontalResolution, bitmap.GetAllFrames.ElementAt(i).HorizontalResolution); + Assert.Equal(expectedVerticalResolution, bitmap.GetAllFrames.ElementAt(i).VerticalResolution); + } + } + #if !NETFRAMEWORK [FactWithAutomaticDisplayName] public void CastMaui_to_AnyBitmap() @@ -920,6 +942,26 @@ public void LoadImage_TiffImage_ShouldLoadWithoutThumbnail() bitmap.FrameCount.Should().Be(1); } + [TheoryWithAutomaticDisplayName] + [InlineData("24_bit.png")] + [InlineData("checkmark.jpg")] + [InlineData("DW-26 Jpg72Input.jpg")] + [InlineData("DW-26 Jpg300Input.jpg")] + [InlineData("mountainclimbers.jpg")] + public void LoadImage_SetPreserveOriginalFormat_ShouldReturnCorrectBitPerPixel(string imageFileName) + { + // Arrange + string imagePath = GetRelativeFilePath(imageFileName); + + // Act + var preserve = new AnyBitmap(imagePath, true); + var notPreserve = new AnyBitmap(imagePath, false); + + // Assert + Assert.Equal(24, preserve.BitsPerPixel); + Assert.Equal(32, notPreserve.BitsPerPixel); + } + #if !NET7_0 [FactWithAutomaticDisplayName] public void CastAnyBitmap_from_SixLabors() diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs index 36294a4..6a756a6 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs @@ -47,10 +47,12 @@ namespace IronSoftware.Drawing public partial class AnyBitmap : IDisposable, IAnyImage { private bool _disposed = false; + private Image Image { get; set; } private byte[] Binary { get; set; } private IImageFormat Format { get; set; } private TiffCompression TiffCompression { get; set; } = TiffCompression.Lzw; + private bool PreserveOriginalFormat { get; set; } = true; /// /// Width of the image. @@ -137,7 +139,7 @@ public MemoryStream GetStream() /// public AnyBitmap Clone() { - return new AnyBitmap(Binary); + return new AnyBitmap(Binary, PreserveOriginalFormat); } /// @@ -463,7 +465,17 @@ public T ToBitmap() /// A Byte Span of image data in any common format. public static AnyBitmap FromSpan(ReadOnlySpan span) { - return new AnyBitmap(span); + return new AnyBitmap(span, true); + } + + /// + /// Create a new Bitmap from a a Byte Span. + /// + /// A Byte Span of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + public static AnyBitmap FromSpan(ReadOnlySpan span, bool preserveOriginalFormat) + { + return new AnyBitmap(span, preserveOriginalFormat); } /// @@ -472,31 +484,64 @@ public static AnyBitmap FromSpan(ReadOnlySpan span) /// A ByteArray of image data in any common format. public static AnyBitmap FromBytes(byte[] bytes) { - return new AnyBitmap(bytes); + return new AnyBitmap(bytes, true); + } + + /// + /// Create a new Bitmap from a a Byte Array. + /// + /// A ByteArray of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + public static AnyBitmap FromBytes(byte[] bytes, bool preserveOriginalFormat) + { + return new AnyBitmap(bytes, preserveOriginalFormat); } /// /// Create a new Bitmap from a (bytes). /// - /// A of image data in any - /// common format. - /// + /// A of image data in any common format. + /// Default is true. Set to false to load as Rgba32. + /// /// public static AnyBitmap FromStream(MemoryStream stream) { - return new AnyBitmap(stream); + return new AnyBitmap(stream, true); + } + + /// + /// Create a new Bitmap from a (bytes). + /// + /// A of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public static AnyBitmap FromStream(MemoryStream stream, bool preserveOriginalFormat) + { + return new AnyBitmap(stream, preserveOriginalFormat); } /// /// Create a new Bitmap from a (bytes). /// - /// A of image data in any - /// common format. - /// + /// A of image data in any common format. + /// /// public static AnyBitmap FromStream(Stream stream) { - return new AnyBitmap(stream); + return new AnyBitmap(stream, true); + } + + /// + /// Create a new Bitmap from a (bytes). + /// + /// A of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public static AnyBitmap FromStream(Stream stream, bool preserveOriginalFormat) + { + return new AnyBitmap(stream, preserveOriginalFormat); } /// @@ -506,42 +551,87 @@ public static AnyBitmap FromStream(Stream stream) /// public AnyBitmap(ReadOnlySpan span) { - LoadImage(span); + LoadImage(span, true); + } + + /// + /// Construct a new Bitmap from binary data (byte span). + /// + /// A byte span of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + public AnyBitmap(ReadOnlySpan span, bool preserveOriginalFormat) + { + LoadImage(span, preserveOriginalFormat); } /// /// Construct a new Bitmap from binary data (bytes). /// /// A ByteArray of image data in any common format. - /// + /// /// public AnyBitmap(byte[] bytes) { - LoadImage(bytes); + LoadImage(bytes, true); + } + + /// + /// Construct a new Bitmap from binary data (bytes). + /// + /// A ByteArray of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public AnyBitmap(byte[] bytes, bool preserveOriginalFormat) + { + LoadImage(bytes, preserveOriginalFormat); } /// /// Construct a new Bitmap from a (bytes). /// - /// A of image data in any - /// common format. - /// + /// A of image data in any common format. + /// /// public AnyBitmap(MemoryStream stream) { - LoadImage(stream.ToArray()); + LoadImage(stream.ToArray(), true); } /// /// Construct a new Bitmap from a (bytes). /// - /// A of image data in any - /// common format. - /// + /// A of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public AnyBitmap(MemoryStream stream, bool preserveOriginalFormat = true) + { + LoadImage(stream.ToArray(), preserveOriginalFormat); + } + + /// + /// Construct a new Bitmap from a (bytes). + /// + /// A of image data in any common format. + /// /// public AnyBitmap(Stream stream) { - LoadImage(stream); + LoadImage(stream, true); + } + + /// + /// Construct a new Bitmap from a (bytes). + /// + /// A of image data in any common format. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public AnyBitmap(Stream stream, bool preserveOriginalFormat) + { + LoadImage(stream, preserveOriginalFormat); } /// @@ -560,25 +650,57 @@ public AnyBitmap(AnyBitmap original, int width, int height) /// Construct a new Bitmap from a file. /// /// A fully qualified file path./ - /// + /// /// public AnyBitmap(string file) { - LoadImage(File.ReadAllBytes(file)); + LoadImage(File.ReadAllBytes(file), true); + } + + /// + /// Construct a new Bitmap from a file. + /// + /// A fully qualified file path./ + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public AnyBitmap(string file, bool preserveOriginalFormat) + { + LoadImage(File.ReadAllBytes(file), preserveOriginalFormat); } /// /// Construct a new Bitmap from a Uri /// /// The uri of the image. - /// + /// /// public AnyBitmap(Uri uri) { try { using Stream stream = LoadUriAsync(uri).GetAwaiter().GetResult(); - LoadImage(stream); + LoadImage(stream, true); + } + catch (Exception e) + { + throw new NotSupportedException("Error while loading AnyBitmap from Uri", e); + } + } + + /// + /// Construct a new Bitmap from a Uri + /// + /// The uri of the image. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public AnyBitmap(Uri uri, bool preserveOriginalFormat) + { + try + { + using Stream stream = LoadUriAsync(uri).GetAwaiter().GetResult(); + LoadImage(stream, preserveOriginalFormat); } catch (Exception e) { @@ -601,17 +723,36 @@ public AnyBitmap(int width, int height, Color backgroundColor = null) /// Create a new Bitmap from a file. /// /// A fully qualified file path. - /// + /// /// public static AnyBitmap FromFile(string file) { if (file.ToLower().EndsWith(".svg")) { - return LoadSVGImage(file); + return LoadSVGImage(file, true); + } + else + { + return new AnyBitmap(file, true); + } + } + + /// + /// Create a new Bitmap from a file. + /// + /// A fully qualified file path. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + public static AnyBitmap FromFile(string file, bool preserveOriginalFormat) + { + if (file.ToLower().EndsWith(".svg")) + { + return LoadSVGImage(file, preserveOriginalFormat); } else { - return new AnyBitmap(file); + return new AnyBitmap(file, preserveOriginalFormat); } } @@ -621,14 +762,14 @@ public static AnyBitmap FromFile(string file) /// The uri of the image. /// /// - /// - /// + /// + /// public static async Task FromUriAsync(Uri uri) { try { using Stream stream = await LoadUriAsync(uri); - return new AnyBitmap(stream); + return new AnyBitmap(stream, true); } catch (Exception e) { @@ -640,9 +781,31 @@ public static async Task FromUriAsync(Uri uri) /// Construct a new Bitmap from a Uri /// /// The uri of the image. + /// Determine whether to load as its original pixel format or Rgba32. /// /// - /// + /// + /// + public static async Task FromUriAsync(Uri uri, bool preserveOriginalFormat) + { + try + { + using Stream stream = await LoadUriAsync(uri); + return new AnyBitmap(stream, preserveOriginalFormat); + } + catch (Exception e) + { + throw new NotSupportedException("Error while loading AnyBitmap from Uri", e); + } + } + + /// + /// Construct a new Bitmap from a Uri + /// + /// The uri of the image. + /// + /// + /// #if NET6_0_OR_GREATER [Obsolete("FromUri(Uri) is obsolete for net60 or greater because it uses WebClient which is obsolete. Consider using FromUriAsync(Uri) method.")] #endif @@ -659,6 +822,30 @@ public static AnyBitmap FromUri(Uri uri) } } + /// + /// Construct a new Bitmap from a Uri + /// + /// The uri of the image. + /// Determine whether to load as its original pixel format or Rgba32. + /// + /// + /// +#if NET6_0_OR_GREATER + [Obsolete("FromUri(Uri) is obsolete for net60 or greater because it uses WebClient which is obsolete. Consider using FromUriAsync(Uri) method.")] +#endif + public static AnyBitmap FromUri(Uri uri, bool preserveOriginalFormat) + { + try + { + using WebClient client = new(); + return new AnyBitmap(client.OpenRead(uri), preserveOriginalFormat); + } + catch (Exception e) + { + throw new NotSupportedException("Error while loading AnyBitmap from Uri", e); + } + } + /// /// Creates an AnyBitmap object from a buffer of RGB pixel data. /// @@ -828,7 +1015,8 @@ public byte[] ExtractAlphaData() { if (BitsPerPixel == 32) { - var alpha = new List(Image.Width * Image.Height); + var alpha = new byte[Image.Width * Image.Height]; + int alphaIndex = 0; using var rgbaImage = Image is Image image ? image : Image.CloneAs(); @@ -836,12 +1024,16 @@ public byte[] ExtractAlphaData() { for (int y = 0; y < accessor.Height; y++) { - Span pixelRow = accessor.GetRowSpan(y); - - for (int x = 0; x < pixelRow.Length; x++) + // Get the row as a span of Rgba32. + Span pixelRow = accessor.GetRowSpan(y); + // Interpret the row as a span of bytes. + Span rowBytes = MemoryMarshal.AsBytes(pixelRow); + + // Each pixel is 4 bytes: R, G, B, A. + // The alpha channel is the fourth byte (index 3, 7, 11, ...). + for (int i = 3; i < rowBytes.Length; i += 4) { - SixLabors.ImageSharp.PixelFormats.Rgba32 pixel = pixelRow[x]; - alpha.Add(pixel.A); + alpha[alphaIndex++] = rowBytes[i]; } } }); @@ -2011,17 +2203,56 @@ private void CreateNewImageInstance(int width, int height, Color backgroundColor Binary = stream.ToArray(); } - private void LoadImage(ReadOnlySpan bytes) + private void LoadImage(ReadOnlySpan bytes, bool preserveOriginalFormat) { Format = Image.DetectFormat(bytes); try { - if(Format is TiffFormat) + if (Format is TiffFormat) OpenTiffToImageSharp(bytes); else { Binary = bytes.ToArray(); - Image = Image.Load(bytes); + + if (preserveOriginalFormat) + Image = Image.Load(bytes); + else + { + PreserveOriginalFormat = preserveOriginalFormat; + Image = Image.Load(bytes); + + // .png image pre-processing + if (Format.Name == "PNG") + Image.Mutate(img => img.BackgroundColor(SixLabors.ImageSharp.Color.White)); + } + + // Fix if the input image is auto-rotated; this issue is acknowledged by SixLabors.ImageSharp community + // ref: https://github.com/SixLabors/ImageSharp/discussions/2685 + Image.Mutate(x => x.AutoOrient()); + + var resolutionUnit = this.Image.Metadata.ResolutionUnits; + var horizontal = this.Image.Metadata.HorizontalResolution; + var vertical = this.Image.Metadata.VerticalResolution; + + // Check if image metadata is accurate already + switch (resolutionUnit) + { + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerMeter: + // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 meter = 37.3701 inches + this.Image.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; + this.Image.Metadata.HorizontalResolution = Math.Ceiling(horizontal / 39.3701); + this.Image.Metadata.VerticalResolution = Math.Ceiling(vertical / 39.3701); + break; + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerCentimeter: + // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 inch = 2.54 centimeters + this.Image.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; + this.Image.Metadata.HorizontalResolution = Math.Ceiling(horizontal * 2.54); + this.Image.Metadata.VerticalResolution = Math.Ceiling(vertical * 2.54); + break; + default: + // No changes required due to teh metadata are accurate already + break; + } } } catch (DllNotFoundException e) @@ -2036,7 +2267,7 @@ private void LoadImage(ReadOnlySpan bytes) } } - private void LoadImage(Stream stream) + private void LoadImage(Stream stream, bool preserveOriginalFormat) { byte[] buffer = new byte[16 * 1024]; using MemoryStream ms = new(); @@ -2046,16 +2277,17 @@ private void LoadImage(Stream stream) ms.Write(buffer, 0, read); } - LoadImage(ms.ToArray()); + LoadImage(ms.ToArray(), preserveOriginalFormat); } - private static AnyBitmap LoadSVGImage(string file) + private static AnyBitmap LoadSVGImage(string file, bool preserveOriginalFormat) { try + { return new AnyBitmap( DecodeSVG(file).Encode(SKEncodedImageFormat.Png, 100) - .ToArray()); + .ToArray(), preserveOriginalFormat); } catch (DllNotFoundException e) { @@ -2201,17 +2433,37 @@ private static SKBitmap OpenTiffToSKBitmap(AnyBitmap anyBitmap) } } + /// + /// Disable warning message written to console by BitMiracle.LibTiff.NET. + /// + private class DisableErrorHandler : TiffErrorHandler + { + public override void WarningHandler(Tiff tif, string method, string format, params object[] args) + { + // do nothing, ie, do not write warnings to console + } + public override void WarningHandlerExt(Tiff tif, object clientData, string method, string format, params object[] args) + { + // do nothing ie, do not write warnings to console + } + } + private void OpenTiffToImageSharp(ReadOnlySpan bytes) { try { int imageWidth = 0; int imageHeight = 0; + double imageXResolution = 0; + double imageYResolution = 0; List images = new(); // create a memory stream out of them using MemoryStream tiffStream = new(bytes.ToArray()); + // Disable warning messages + Tiff.SetErrorHandler(new DisableErrorHandler()); + // open a TIFF stored in the stream using (Tiff tiff = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream())) { @@ -2227,7 +2479,7 @@ private void OpenTiffToImageSharp(ReadOnlySpan bytes) continue; } - var (width, height) = SetWidthHeight(tiff, i, ref imageWidth, ref imageHeight); + var (width, height, horizontalResolution, verticalResolution) = SetWidthHeight(tiff, i, ref imageWidth, ref imageHeight, ref imageXResolution, ref imageYResolution); // Read the image into the memory buffer int[] raster = new int[height * width]; @@ -2241,9 +2493,15 @@ private void OpenTiffToImageSharp(ReadOnlySpan bytes) var bits = PrepareByteArray(bmp, raster, width, height); images.Add(Image.LoadPixelData(bits, bmp.Width, bmp.Height)); + + // Update the metadata for image resolutions + images[0].Metadata.HorizontalResolution = horizontalResolution; + images[0].Metadata.VerticalResolution = verticalResolution; } } + + // find max FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight); @@ -2332,27 +2590,42 @@ private bool IsThumbnail(Tiff tiff) private ReadOnlySpan PrepareByteArray(Image bmp, int[] raster, int width, int height) { - byte[] bits = new byte[GetStride(bmp) * height]; + int stride = GetStride(bmp); + byte[] bits = new byte[stride * height]; - for (int y = 0; y < height; y++) + // If no extra padding exists, copy entire rows at once. + if (stride == width * 4 && true) { - int rasterOffset = y * width; - int bitsOffset = (height - y - 1) * GetStride(bmp); - - for (int x = 0; x < width; x++) + int bytesPerRow = stride; + for (int y = 0; y < height; y++) + { + int srcByteIndex = y * bytesPerRow; + int destByteIndex = (height - y - 1) * bytesPerRow; + Buffer.BlockCopy(raster, srcByteIndex, bits, destByteIndex, bytesPerRow); + } + } + else + { + // Fallback to per-pixel processing if stride includes padding. + for (int y = 0; y < height; y++) { - int rgba = raster[rasterOffset++]; - bits[bitsOffset++] = (byte)(rgba & 0xff); // R - bits[bitsOffset++] = (byte)((rgba >> 8) & 0xff); // G - bits[bitsOffset++] = (byte)((rgba >> 16) & 0xff); // B - bits[bitsOffset++] = (byte)((rgba >> 24) & 0xff); // A + int rasterOffset = y * width; + int bitsOffset = (height - y - 1) * stride; + for (int x = 0; x < width; x++) + { + int rgba = raster[rasterOffset++]; + bits[bitsOffset++] = (byte)(rgba & 0xff); // R + bits[bitsOffset++] = (byte)((rgba >> 8) & 0xff); // G + bits[bitsOffset++] = (byte)((rgba >> 16) & 0xff); // B + bits[bitsOffset++] = (byte)((rgba >> 24) & 0xff); // A + } } } return bits; } - private (int width, int height) SetWidthHeight(Tiff tiff, short index, ref int imageWidth, ref int imageHeight) + private (int width, int height, double horizontalResolution, double verticalResolution) SetWidthHeight(Tiff tiff, short index, ref int imageWidth, ref int imageHeight, ref double imageXResolution, ref double imageYResolution) { // Find the width and height of the image FieldValue[] value = tiff.GetField(TiffTag.IMAGEWIDTH); @@ -2361,6 +2634,13 @@ private ReadOnlySpan PrepareByteArray(Image bmp, int[] raster, int value = tiff.GetField(TiffTag.IMAGELENGTH); int height = value[0].ToInt(); + // If resolutions are null due to damaged files, return the default value of 96 + value = tiff.GetField(TiffTag.XRESOLUTION); + double horizontalResolution = Math.Floor(value?.FirstOrDefault().ToDouble() ?? 96); + + value = tiff.GetField(TiffTag.YRESOLUTION); + double verticalResolution = Math.Floor(value?.FirstOrDefault().ToDouble() ?? 96); + if (index == 0) { if (width == 0 || height == 0) @@ -2385,7 +2665,7 @@ private ReadOnlySpan PrepareByteArray(Image bmp, int[] raster, int } } - return (width, height); + return (width, height, horizontalResolution, verticalResolution); } private static List CreateAnyBitmaps(IEnumerable imagePaths) diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj b/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj index 8c7cfdf..ef6c50a 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj @@ -1,4 +1,4 @@ - + IronSoftware.Drawing.Common.snk @@ -25,7 +25,7 @@ - + @@ -35,13 +35,13 @@ - - + + - + diff --git a/NuGet/EULA.txt b/NuGet/EULA.txt index b8165ba..ed552ff 100644 --- a/NuGet/EULA.txt +++ b/NuGet/EULA.txt @@ -3,7 +3,7 @@ Iron Drawing End User License Agreement License Agreement Terms and Conditions -This License Agreement (“Agreement”) is by Iron Software LLC (“Iron”) and the person or entity licensing the Iron Software (“Company”). This Agreement consists of each Order and these Terms and Conditions (“Terms”). +This License Agreement (“Agreement”) is by Iron Software (“Iron”) and the person or entity licensing the Iron Software (“Company”). This Agreement consists of each Order and these Terms and Conditions (“Terms”). 1. Overview a. Iron is the owner and licensor of several software libraries and packages, as described by Iron on Iron’s website and in Iron’s marketing materials (the “Iron Software”). Iron offers Company one or more options to use the Iron Software as documented by Iron subject to the restrictions stated in this Agreement. Iron may also provide support services (“Support and Updates”) and other services, including consulting services regarding Company’s use of the Iron Software (collectively with Support and Updates, the “Services”). @@ -130,4 +130,4 @@ j. Independent Contractor. The relationship of the parties is that of independen k. Force Majeure. Except for a party’s payment obligations, neither party shall have any liability for any defaults or delays resulting from circumstances reasonably beyond its reasonable control. -l. Company Documents. The provisions and terms of any document issued by Company in conjunction with this Agreement shall be of no effect and shall not in any way extend, affect or amend the terms and conditions set forth in this Agreement (including any Order) unless expressly accepted in writing by Iron. \ No newline at end of file +l. Company Documents. The provisions and terms of any document issued by Company in conjunction with this Agreement shall be of no effect and shall not in any way extend, affect or amend the terms and conditions set forth in this Agreement (including any Order) unless expressly accepted in writing by Iron. diff --git a/NuGet/IronSoftware.Drawing.nuspec b/NuGet/IronSoftware.Drawing.nuspec index 81aaa71..7183b4e 100644 --- a/NuGet/IronSoftware.Drawing.nuspec +++ b/NuGet/IronSoftware.Drawing.nuspec @@ -39,35 +39,27 @@ Supports: For general support and technical inquiries, please email us at: support@ironsoftware.com IronSoftware.System.Drawing is an open-source solution for .NET developers to replace System.Drawing.Common with a universal and flexible library. - - Internal improvement of the library functionality - Copyright © Iron Software 2022-2024 + - Updates internal dependencies. + - Fixes the issue of some images are auto-rotated when loading as AnyBitmap. + - Adds an option called preserveOriginalFormat to download input image as Rgba32 via AnyBitmap. + Copyright © Iron Software 2022-2025 Images, Bitmap, SkiaSharp, SixLabors, BitMiracle, Maui, SVG, TIFF, TIF, GIF, JPEG, PNG, Color, Rectangle, Drawing, C#, VB.NET, ASPX, create, render, generate, standard, netstandard2.0, core, netcore - - + + - - - + + + - - - - - - - - - -