Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CI/job_templates/test_drawing_libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
149 changes: 102 additions & 47 deletions IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Width of the image.
Expand Down Expand Up @@ -138,7 +139,7 @@ public MemoryStream GetStream()
/// <returns></returns>
public AnyBitmap Clone()
{
return new AnyBitmap(Binary);
return new AnyBitmap(Binary, PreserveOriginalFormat);
}

/// <summary>
Expand Down Expand Up @@ -462,87 +463,99 @@ public T ToBitmap<T>()
/// Create a new Bitmap from a a Byte Span.
/// </summary>
/// <param name="span">A Byte Span of image data in any common format.</param>
public static AnyBitmap FromSpan(ReadOnlySpan<byte> span)
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
public static AnyBitmap FromSpan(ReadOnlySpan<byte> span, bool preserveOriginalFormat = true)
{
return new AnyBitmap(span);
return new AnyBitmap(span, preserveOriginalFormat);
}

/// <summary>
/// Create a new Bitmap from a a Byte Array.
/// </summary>
/// <param name="bytes">A ByteArray of image data in any common format.</param>
public static AnyBitmap FromBytes(byte[] bytes)
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
public static AnyBitmap FromBytes(byte[] bytes, bool preserveOriginalFormat = true)
{
return new AnyBitmap(bytes);
return new AnyBitmap(bytes, preserveOriginalFormat);
}

/// <summary>
/// Create a new Bitmap from a <see cref="Stream"/> (bytes).
/// </summary>
/// <param name="stream">A <see cref="Stream"/> of image data in any
/// common format.</param>
/// <seealso cref="FromStream(Stream)"/>
/// <param name="stream">A <see cref="Stream"/> of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromStream(Stream, bool)"/>
/// <seealso cref="AnyBitmap"/>
public static AnyBitmap FromStream(MemoryStream stream)
public static AnyBitmap FromStream(MemoryStream stream, bool preserveOriginalFormat = true)
{
return new AnyBitmap(stream);
return new AnyBitmap(stream, preserveOriginalFormat);
}

/// <summary>
/// Create a new Bitmap from a <see cref="Stream"/> (bytes).
/// </summary>
/// <param name="stream">A <see cref="Stream"/> of image data in any
/// common format.</param>
/// <seealso cref="FromStream(MemoryStream)"/>
/// <param name="stream">A <see cref="Stream"/> of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromStream(MemoryStream, bool)"/>
/// <seealso cref="AnyBitmap"/>
public static AnyBitmap FromStream(Stream stream)
public static AnyBitmap FromStream(Stream stream, bool preserveOriginalFormat = true)
{
return new AnyBitmap(stream);
return new AnyBitmap(stream, preserveOriginalFormat);
}

/// <summary>
/// Construct a new Bitmap from binary data (byte span).
/// </summary>
/// <param name="span">A byte span of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(ReadOnlySpan<byte> span)
public AnyBitmap(ReadOnlySpan<byte> span, bool preserveOriginalFormat = true)
{
LoadImage(span);
LoadImage(span, preserveOriginalFormat);
}

/// <summary>
/// Construct a new Bitmap from binary data (bytes).
/// </summary>
/// <param name="bytes">A ByteArray of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromBytes"/>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(byte[] bytes)
public AnyBitmap(byte[] bytes, bool preserveOriginalFormat = true)
{
LoadImage(bytes);
LoadImage(bytes, preserveOriginalFormat);
}

/// <summary>
/// Construct a new Bitmap from a <see cref="Stream"/> (bytes).
/// </summary>
/// <param name="stream">A <see cref="Stream"/> of image data in any
/// common format.</param>
/// <seealso cref="FromStream(Stream)"/>
/// <param name="stream">A <see cref="Stream"/> of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromStream(Stream, bool)"/>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(MemoryStream stream)
public AnyBitmap(MemoryStream stream, bool preserveOriginalFormat = true)
{
LoadImage(stream.ToArray());
LoadImage(stream.ToArray(), preserveOriginalFormat);
}

/// <summary>
/// Construct a new Bitmap from a <see cref="Stream"/> (bytes).
/// </summary>
/// <param name="stream">A <see cref="Stream"/> of image data in any
/// common format.</param>
/// <seealso cref="FromStream(MemoryStream)"/>
/// <param name="stream">A <see cref="Stream"/> of image data in any common format.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromStream(MemoryStream, bool)"/>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(Stream stream)
public AnyBitmap(Stream stream, bool preserveOriginalFormat = true)
{
LoadImage(stream);
LoadImage(stream, preserveOriginalFormat);
}

/// <summary>
Expand All @@ -561,25 +574,29 @@ public AnyBitmap(AnyBitmap original, int width, int height)
/// Construct a new Bitmap from a file.
/// </summary>
/// <param name="file">A fully qualified file path./</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromFile"/>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(string file)
public AnyBitmap(string file, bool preserveOriginalFormat = true)
{
LoadImage(File.ReadAllBytes(file));
LoadImage(File.ReadAllBytes(file), preserveOriginalFormat);
}

/// <summary>
/// Construct a new Bitmap from a Uri
/// </summary>
/// <param name="uri">The uri of the image.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromUriAsync"/>
/// <seealso cref="AnyBitmap"/>
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)
{
Expand All @@ -602,34 +619,38 @@ public AnyBitmap(int width, int height, Color backgroundColor = null)
/// Create a new Bitmap from a file.
/// </summary>
/// <param name="file">A fully qualified file path.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <seealso cref="FromFile"/>
/// <seealso cref="AnyBitmap"/>
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);
}
}

/// <summary>
/// Construct a new Bitmap from a Uri
/// </summary>
/// <param name="uri">The uri of the image.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <returns></returns>
/// <seealso cref="AnyBitmap"/>
/// <seealso cref="FromUri"/>
/// <seealso cref="FromUriAsync"/>
public static async Task<AnyBitmap> FromUriAsync(Uri uri)
public static async Task<AnyBitmap> 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)
{
Expand All @@ -641,13 +662,15 @@ public static async Task<AnyBitmap> FromUriAsync(Uri uri)
/// Construct a new Bitmap from a Uri
/// </summary>
/// <param name="uri">The uri of the image.</param>
/// <param name="preserveOriginalFormat">Determine whether to load <see cref="SixLabors.ImageSharp.Image"/> as its original pixel format or Rgba32.
/// Default is true. Set to false to load as Rgba32.</param>
/// <returns></returns>
/// <seealso cref="AnyBitmap"/>
/// <seealso cref="FromUriAsync"/>
#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
{
Expand Down Expand Up @@ -2017,19 +2040,33 @@ private void CreateNewImageInstance(int width, int height, Color backgroundColor
Binary = stream.ToArray();
}

private void LoadImage(ReadOnlySpan<byte> bytes)
private void LoadImage(ReadOnlySpan<byte> 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<Rgba32>(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;
Expand Down Expand Up @@ -2067,7 +2104,7 @@ private void LoadImage(ReadOnlySpan<byte> bytes)
}
}

private void LoadImage(Stream stream)
private void LoadImage(Stream stream, bool preserveOriginalFormat)
{
byte[] buffer = new byte[16 * 1024];
using MemoryStream ms = new();
Expand All @@ -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)
{
Expand Down Expand Up @@ -2233,6 +2270,21 @@ private static SKBitmap OpenTiffToSKBitmap(AnyBitmap anyBitmap)
}
}

/// <summary>
/// Disable warning message written to console by BitMiracle.LibTiff.NET.
/// </summary>
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<byte> bytes)
{
try
Expand All @@ -2246,6 +2298,9 @@ private void OpenTiffToImageSharp(ReadOnlySpan<byte> 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()))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyOriginatorKeyFile>IronSoftware.Drawing.Common.snk</AssemblyOriginatorKeyFile>
Expand All @@ -25,7 +25,7 @@
<PackageReference Include="BitMiracle.LibTiff.NET" Version="2.4.649" />
<PackageReference Include="Microsoft.Maui.Graphics" Version="7.0.92" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="IronSoftware.Drawing.Abstractions" Version="2025.2.6" />
<PackageReference Include="IronSoftware.Drawing.Abstractions" Version="2025.4.2" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
Expand Down
Loading