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/UnitTests/AnyBitmapFunctionality.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs index b563d9b..83f30a5 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs @@ -942,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 94503d4..5f3a22d 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs @@ -52,6 +52,7 @@ public partial class AnyBitmap : IDisposable, IAnyImage 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. @@ -138,7 +139,7 @@ public MemoryStream GetStream() /// public AnyBitmap Clone() { - return new AnyBitmap(Binary); + return new AnyBitmap(Binary, PreserveOriginalFormat); } /// @@ -462,87 +463,99 @@ public T ToBitmap() /// Create a new Bitmap from a a Byte Span. /// /// A Byte Span of image data in any common format. - public static AnyBitmap FromSpan(ReadOnlySpan span) + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. + public static AnyBitmap FromSpan(ReadOnlySpan span, bool preserveOriginalFormat = true) { - return new AnyBitmap(span); + return new AnyBitmap(span, preserveOriginalFormat); } /// /// Create a new Bitmap from a a Byte Array. /// /// A ByteArray of image data in any common format. - public static AnyBitmap FromBytes(byte[] bytes) + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. + public static AnyBitmap FromBytes(byte[] bytes, bool preserveOriginalFormat = true) { - return new AnyBitmap(bytes); + 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. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. + /// /// - public static AnyBitmap FromStream(MemoryStream stream) + public static AnyBitmap FromStream(MemoryStream stream, bool preserveOriginalFormat = true) { - return new AnyBitmap(stream); + 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. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. + /// /// - public static AnyBitmap FromStream(Stream stream) + public static AnyBitmap FromStream(Stream stream, bool preserveOriginalFormat = true) { - return new AnyBitmap(stream); + return new AnyBitmap(stream, preserveOriginalFormat); } /// /// 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. + /// Default is true. Set to false to load as Rgba32. /// - public AnyBitmap(ReadOnlySpan span) + public AnyBitmap(ReadOnlySpan span, bool preserveOriginalFormat = true) { - LoadImage(span); + LoadImage(span, preserveOriginalFormat); } /// /// 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. + /// Default is true. Set to false to load as Rgba32. /// /// - public AnyBitmap(byte[] bytes) + public AnyBitmap(byte[] bytes, bool preserveOriginalFormat = true) { - LoadImage(bytes); + 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. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. + /// /// - public AnyBitmap(MemoryStream stream) + public AnyBitmap(MemoryStream stream, bool preserveOriginalFormat = true) { - LoadImage(stream.ToArray()); + LoadImage(stream.ToArray(), preserveOriginalFormat); } /// /// 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. + /// Default is true. Set to false to load as Rgba32. + /// /// - public AnyBitmap(Stream stream) + public AnyBitmap(Stream stream, bool preserveOriginalFormat = true) { - LoadImage(stream); + LoadImage(stream, preserveOriginalFormat); } /// @@ -561,25 +574,29 @@ public AnyBitmap(AnyBitmap original, int width, int height) /// Construct a new Bitmap from a file. /// /// A fully qualified file path./ + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. /// /// - public AnyBitmap(string file) + public AnyBitmap(string file, bool preserveOriginalFormat = true) { - LoadImage(File.ReadAllBytes(file)); + LoadImage(File.ReadAllBytes(file), preserveOriginalFormat); } /// /// Construct a new Bitmap from a Uri /// /// The uri of the image. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. /// /// - public AnyBitmap(Uri uri) + public AnyBitmap(Uri uri, bool preserveOriginalFormat = true) { try { using Stream stream = LoadUriAsync(uri).GetAwaiter().GetResult(); - LoadImage(stream); + LoadImage(stream, preserveOriginalFormat); } catch (Exception e) { @@ -602,17 +619,19 @@ public AnyBitmap(int width, int height, Color backgroundColor = null) /// Create a new Bitmap from a file. /// /// A fully qualified file path. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. /// /// - public static AnyBitmap FromFile(string file) + public static AnyBitmap FromFile(string file, bool preserveOriginalFormat = true) { if (file.ToLower().EndsWith(".svg")) { - return LoadSVGImage(file); + return LoadSVGImage(file, preserveOriginalFormat); } else { - return new AnyBitmap(file); + return new AnyBitmap(file, preserveOriginalFormat); } } @@ -620,16 +639,18 @@ public static AnyBitmap FromFile(string file) /// Construct a new Bitmap from a Uri /// /// The uri of the image. + /// Determine whether to load as its original pixel format or Rgba32. + /// Default is true. Set to false to load as Rgba32. /// /// /// /// - public static async Task FromUriAsync(Uri uri) + public static async Task FromUriAsync(Uri uri, bool preserveOriginalFormat = true) { try { using Stream stream = await LoadUriAsync(uri); - return new AnyBitmap(stream); + return new AnyBitmap(stream, preserveOriginalFormat); } catch (Exception e) { @@ -641,13 +662,15 @@ 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. + /// Default is true. Set to false to load as 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) + public static AnyBitmap FromUri(Uri uri, bool preserveOriginalFormat = true) { try { @@ -2017,19 +2040,33 @@ 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) 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; @@ -2067,7 +2104,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(); @@ -2077,17 +2114,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) { @@ -2233,6 +2270,21 @@ 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 @@ -2246,6 +2298,9 @@ private void OpenTiffToImageSharp(ReadOnlySpan bytes) // 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())) { diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj b/IronSoftware.Drawing/IronSoftware.Drawing.Common/IronSoftware.Drawing.Common.csproj index af58cb4..ec774df 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 @@ - + diff --git a/NuGet/IronSoftware.Drawing.nuspec b/NuGet/IronSoftware.Drawing.nuspec index 730882b..0946583 100644 --- a/NuGet/IronSoftware.Drawing.nuspec +++ b/NuGet/IronSoftware.Drawing.nuspec @@ -39,23 +39,23 @@ 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. - - Fixes incorrect HorizontalResolution and VerticalResolution of AnyBitmap images. - - Improves library's internal functionality and performance. + - Updates internal dependencies. + - Disable warning messages from BitMiracle.LibTiff.NET. 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 - + - - - + + +