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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\IronSoftware.Drawing.Common\IronSoftware.Drawing.Common.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// See https://aka.ms/new-console-template for more information
Copy link
Copy Markdown
Contributor

@wasin-ironsoftware wasin-ironsoftware Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good that we have test!

Alternatively, I think we could get away by grabbing memory usage information from either GC or System.Diagnostics, and validate if it doesn't reach higher than what rough goal we set. Because I think having a separate console app, very less chance others will know or will test against it.


using IronSoftware.Drawing;

ReadOnlySpan<byte> bytes = File.ReadAllBytes("test.bmp");

for (int i=0; i<10000; i++)
{
using AnyBitmap bmp = new AnyBitmap(bytes);
var bin = bmp.GetBytes();
Console.WriteLine(bin.Length);
}
143 changes: 68 additions & 75 deletions IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;

namespace IronSoftware.Drawing
Expand Down Expand Up @@ -451,12 +452,19 @@ public T ToBitmap<T>()
}
}

/// <summary>
/// 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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can say "Wow" every time I see the word Span<T> in managed code :D

{
return new AnyBitmap(span);
}

/// <summary>
/// Create a new Bitmap from a a Byte Array.
/// </summary>
/// <param name="bytes">A ByteArray of image data in any common format.</param>
/// <seealso cref="FromBytes"/>
/// <seealso cref="AnyBitmap(byte[])"/>
public static AnyBitmap FromBytes(byte[] bytes)
{
return new AnyBitmap(bytes);
Expand Down Expand Up @@ -485,6 +493,17 @@ public static AnyBitmap FromStream(Stream stream)
{
return new AnyBitmap(stream);
}

/// <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>
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(ReadOnlySpan<byte> span)
{
LoadImage(span);
}

/// <summary>
/// Construct a new Bitmap from binary data (bytes).
/// </summary>
Expand Down Expand Up @@ -540,7 +559,7 @@ public AnyBitmap(AnyBitmap original, int width, int height)
/// <seealso cref="AnyBitmap"/>
public AnyBitmap(string file)
{
LoadImage(file);
LoadImage(File.ReadAllBytes(file));
}

/// <summary>
Expand Down Expand Up @@ -1986,68 +2005,29 @@ private void CreateNewImageInstance(int width, int height, Color backgroundColor
Image.SaveAsBmp(stream);
Binary = stream.ToArray();
}

private void LoadImage(byte[] bytes)
private void LoadImage(ReadOnlySpan<byte> bytes)
{
Format = Image.DetectFormat(bytes);
try
{
#if NET6_0_OR_GREATER
Image = Image.Load(bytes);
Format = Image.Metadata.DecodedImageFormat;
#else
Image = Image.Load(bytes, out IImageFormat format);
Format = format;
#endif
Binary = bytes;
}
catch (DllNotFoundException e)
{
throw new DllNotFoundException(
"Please install SixLabors.ImageSharp from NuGet.", e);
}
catch (Exception)
{
try
{
if(Format is TiffFormat)
OpenTiffToImageSharp(bytes);
}
catch (Exception e)
else
{
throw new NotSupportedException(
"Image could not be loaded. File format is not supported.", e);
Binary = bytes.ToArray();
Image = Image.Load(bytes);
}
}
}

private void LoadImage(string file)
{
try
{
#if NET6_0_OR_GREATER
Image = Image.Load(file);
Format = Image.Metadata.DecodedImageFormat;
#else
Image = Image.Load(file, out IImageFormat format);
Format = format;
#endif
Binary = File.ReadAllBytes(file);
}
catch (DllNotFoundException e)
{
throw new DllNotFoundException(
"Please install SixLabors.ImageSharp from NuGet.", e);
}
catch (Exception)
catch (Exception e)
{
try
{
OpenTiffToImageSharp(File.ReadAllBytes(file));
}
catch (Exception e)
{
throw new NotSupportedException(
"Image could not be loaded. File format is not supported.", e);
}
throw new NotSupportedException(
"Image could not be loaded. File format is not supported.", e);
}
}

Expand All @@ -2064,14 +2044,6 @@ private void LoadImage(Stream stream)
LoadImage(ms.ToArray());
}

private void SetBinaryFromImageSharp(Image<Rgba32> tiffImage)
{
using var memoryStream = new MemoryStream();
tiffImage.Save(memoryStream, new TiffEncoder());
_ = memoryStream.Seek(0, SeekOrigin.Begin);
LoadImage(memoryStream);
}

private static AnyBitmap LoadSVGImage(string file)
{
try
Expand Down Expand Up @@ -2224,7 +2196,7 @@ private static SKBitmap OpenTiffToSKBitmap(AnyBitmap anyBitmap)
}
}

private void OpenTiffToImageSharp(byte[] bytes)
private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
{
try
{
Expand All @@ -2233,7 +2205,7 @@ private void OpenTiffToImageSharp(byte[] bytes)
List<Image> images = new();

// create a memory stream out of them
using MemoryStream tiffStream = new(bytes);
using MemoryStream tiffStream = new(bytes.ToArray());

// open a TIFF stored in the stream
using (var tif = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
Expand All @@ -2254,29 +2226,50 @@ private void OpenTiffToImageSharp(byte[] bytes)

using Image<Rgba32> bmp = new(width, height);

byte[] bits = PrepareByteArray(bmp, raster, width, height);
var bits = PrepareByteArray(bmp, raster, width, height);

images.Add(Image.LoadPixelData<Rgba32>(bits, bmp.Width, bmp.Height));
}
}

Image?.Dispose();


// find max
FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight);

using Image<Rgba32> tiffImage = CloneAndResizeImageSharp(images[0], maxWidth, maxHeight);
// mute first image
images[0].Mutate(img => img.Resize(new ResizeOptions
{
Size = new Size(maxWidth, maxHeight),
Mode = ResizeMode.BoxPad,
PadColor = SixLabors.ImageSharp.Color.Transparent
}));

// iterate through images past the first
for (int i = 1; i < images.Count; i++)
{
Image<Rgba32> image = CloneAndResizeImageSharp(images[i], maxWidth, maxHeight);
_ = tiffImage.Frames.AddFrame(image.Frames.RootFrame);
}
// mute image
images[i].Mutate(img => img.Resize(new ResizeOptions
{
Size = new Size(maxWidth, maxHeight),
Mode = ResizeMode.BoxPad,
PadColor = SixLabors.ImageSharp.Color.Transparent
}));

SetBinaryFromImageSharp(tiffImage);
// add frames to first image
_ = images[0].Frames.AddFrame(images[i].Frames.RootFrame);

foreach (Image image in images)
{
image.Dispose();
// dispose images past the first
images[i].Dispose();
}

// get raw binary
using var memoryStream = new MemoryStream();
images[0].Save(memoryStream, new TiffEncoder());
memoryStream.Seek(0, SeekOrigin.Begin);

// store result
Binary = memoryStream.ToArray();
Image?.Dispose();
Image = images[0];
}
catch (DllNotFoundException e)
{
Expand All @@ -2288,7 +2281,7 @@ private void OpenTiffToImageSharp(byte[] bytes)
}
}

private byte[] PrepareByteArray(Image<Rgba32> bmp, int[] raster, int width, int height)
private ReadOnlySpan<byte> PrepareByteArray(Image<Rgba32> bmp, int[] raster, int width, int height)
{
byte[] bits = new byte[GetStride(bmp) * height];

Expand Down
6 changes: 6 additions & 0 deletions IronSoftware.Drawing/IronSoftware.Drawing.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "job_templates", "job_templa
..\CI\job_templates\test_drawing_libraries.yml = ..\CI\job_templates\test_drawing_libraries.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IronSoftware.Drawing.Common.ConsoleTest", "IronSoftware.Drawing.Common.ConsoleTest\IronSoftware.Drawing.Common.ConsoleTest.csproj", "{A0B0F474-108B-4B60-834B-8FB169743687}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -57,6 +59,10 @@ Global
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Release|Any CPU.Build.0 = Release|Any CPU
{A0B0F474-108B-4B60-834B-8FB169743687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0B0F474-108B-4B60-834B-8FB169743687}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0B0F474-108B-4B60-834B-8FB169743687}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0B0F474-108B-4B60-834B-8FB169743687}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down