Skip to content

Commit 6efff42

Browse files
Memory Improvements (#90)
* add console app for easy perf testing add console app for easy perf testing * memory optimizations add support for ReadOnlySpan; use Image.DetectFormat(); reduce copying when resizing tiffs * CI: Merge all ci into one pr validation & install NET8 before buildling --------- Co-authored-by: Meee <mee@ironsoftware.com>
1 parent 0ee5a71 commit 6efff42

4 files changed

Lines changed: 100 additions & 75 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\IronSoftware.Drawing.Common\IronSoftware.Drawing.Common.csproj" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// See https://aka.ms/new-console-template for more information
2+
3+
using IronSoftware.Drawing;
4+
5+
ReadOnlySpan<byte> bytes = File.ReadAllBytes("test.bmp");
6+
7+
for (int i=0; i<10000; i++)
8+
{
9+
using AnyBitmap bmp = new AnyBitmap(bytes);
10+
var bin = bmp.GetBytes();
11+
Console.WriteLine(bin.Length);
12+
}

IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs

Lines changed: 68 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Net.Http;
2222
using System.Runtime.CompilerServices;
2323
using System.Runtime.InteropServices;
24+
using System.Runtime.InteropServices.ComTypes;
2425
using System.Threading.Tasks;
2526

2627
namespace IronSoftware.Drawing
@@ -451,12 +452,19 @@ public T ToBitmap<T>()
451452
}
452453
}
453454

455+
/// <summary>
456+
/// Create a new Bitmap from a a Byte Span.
457+
/// </summary>
458+
/// <param name="span">A Byte Span of image data in any common format.</param>
459+
public static AnyBitmap FromSpan(ReadOnlySpan<byte> span)
460+
{
461+
return new AnyBitmap(span);
462+
}
463+
454464
/// <summary>
455465
/// Create a new Bitmap from a a Byte Array.
456466
/// </summary>
457467
/// <param name="bytes">A ByteArray of image data in any common format.</param>
458-
/// <seealso cref="FromBytes"/>
459-
/// <seealso cref="AnyBitmap(byte[])"/>
460468
public static AnyBitmap FromBytes(byte[] bytes)
461469
{
462470
return new AnyBitmap(bytes);
@@ -485,6 +493,17 @@ public static AnyBitmap FromStream(Stream stream)
485493
{
486494
return new AnyBitmap(stream);
487495
}
496+
497+
/// <summary>
498+
/// Construct a new Bitmap from binary data (byte span).
499+
/// </summary>
500+
/// <param name="span">A byte span of image data in any common format.</param>
501+
/// <seealso cref="AnyBitmap"/>
502+
public AnyBitmap(ReadOnlySpan<byte> span)
503+
{
504+
LoadImage(span);
505+
}
506+
488507
/// <summary>
489508
/// Construct a new Bitmap from binary data (bytes).
490509
/// </summary>
@@ -540,7 +559,7 @@ public AnyBitmap(AnyBitmap original, int width, int height)
540559
/// <seealso cref="AnyBitmap"/>
541560
public AnyBitmap(string file)
542561
{
543-
LoadImage(file);
562+
LoadImage(File.ReadAllBytes(file));
544563
}
545564

546565
/// <summary>
@@ -1986,68 +2005,29 @@ private void CreateNewImageInstance(int width, int height, Color backgroundColor
19862005
Image.SaveAsBmp(stream);
19872006
Binary = stream.ToArray();
19882007
}
1989-
1990-
private void LoadImage(byte[] bytes)
2008+
2009+
private void LoadImage(ReadOnlySpan<byte> bytes)
19912010
{
2011+
Format = Image.DetectFormat(bytes);
19922012
try
19932013
{
1994-
#if NET6_0_OR_GREATER
1995-
Image = Image.Load(bytes);
1996-
Format = Image.Metadata.DecodedImageFormat;
1997-
#else
1998-
Image = Image.Load(bytes, out IImageFormat format);
1999-
Format = format;
2000-
#endif
2001-
Binary = bytes;
2002-
}
2003-
catch (DllNotFoundException e)
2004-
{
2005-
throw new DllNotFoundException(
2006-
"Please install SixLabors.ImageSharp from NuGet.", e);
2007-
}
2008-
catch (Exception)
2009-
{
2010-
try
2011-
{
2014+
if(Format is TiffFormat)
20122015
OpenTiffToImageSharp(bytes);
2013-
}
2014-
catch (Exception e)
2016+
else
20152017
{
2016-
throw new NotSupportedException(
2017-
"Image could not be loaded. File format is not supported.", e);
2018+
Binary = bytes.ToArray();
2019+
Image = Image.Load(bytes);
20182020
}
20192021
}
2020-
}
2021-
2022-
private void LoadImage(string file)
2023-
{
2024-
try
2025-
{
2026-
#if NET6_0_OR_GREATER
2027-
Image = Image.Load(file);
2028-
Format = Image.Metadata.DecodedImageFormat;
2029-
#else
2030-
Image = Image.Load(file, out IImageFormat format);
2031-
Format = format;
2032-
#endif
2033-
Binary = File.ReadAllBytes(file);
2034-
}
20352022
catch (DllNotFoundException e)
20362023
{
20372024
throw new DllNotFoundException(
20382025
"Please install SixLabors.ImageSharp from NuGet.", e);
20392026
}
2040-
catch (Exception)
2027+
catch (Exception e)
20412028
{
2042-
try
2043-
{
2044-
OpenTiffToImageSharp(File.ReadAllBytes(file));
2045-
}
2046-
catch (Exception e)
2047-
{
2048-
throw new NotSupportedException(
2049-
"Image could not be loaded. File format is not supported.", e);
2050-
}
2029+
throw new NotSupportedException(
2030+
"Image could not be loaded. File format is not supported.", e);
20512031
}
20522032
}
20532033

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

2067-
private void SetBinaryFromImageSharp(Image<Rgba32> tiffImage)
2068-
{
2069-
using var memoryStream = new MemoryStream();
2070-
tiffImage.Save(memoryStream, new TiffEncoder());
2071-
_ = memoryStream.Seek(0, SeekOrigin.Begin);
2072-
LoadImage(memoryStream);
2073-
}
2074-
20752047
private static AnyBitmap LoadSVGImage(string file)
20762048
{
20772049
try
@@ -2224,7 +2196,7 @@ private static SKBitmap OpenTiffToSKBitmap(AnyBitmap anyBitmap)
22242196
}
22252197
}
22262198

2227-
private void OpenTiffToImageSharp(byte[] bytes)
2199+
private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
22282200
{
22292201
try
22302202
{
@@ -2233,7 +2205,7 @@ private void OpenTiffToImageSharp(byte[] bytes)
22332205
List<Image> images = new();
22342206

22352207
// create a memory stream out of them
2236-
using MemoryStream tiffStream = new(bytes);
2208+
using MemoryStream tiffStream = new(bytes.ToArray());
22372209

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

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

2257-
byte[] bits = PrepareByteArray(bmp, raster, width, height);
2229+
var bits = PrepareByteArray(bmp, raster, width, height);
22582230

22592231
images.Add(Image.LoadPixelData<Rgba32>(bits, bmp.Width, bmp.Height));
22602232
}
22612233
}
2262-
2263-
Image?.Dispose();
2264-
2234+
2235+
// find max
22652236
FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight);
22662237

2267-
using Image<Rgba32> tiffImage = CloneAndResizeImageSharp(images[0], maxWidth, maxHeight);
2238+
// mute first image
2239+
images[0].Mutate(img => img.Resize(new ResizeOptions
2240+
{
2241+
Size = new Size(maxWidth, maxHeight),
2242+
Mode = ResizeMode.BoxPad,
2243+
PadColor = SixLabors.ImageSharp.Color.Transparent
2244+
}));
2245+
2246+
// iterate through images past the first
22682247
for (int i = 1; i < images.Count; i++)
22692248
{
2270-
Image<Rgba32> image = CloneAndResizeImageSharp(images[i], maxWidth, maxHeight);
2271-
_ = tiffImage.Frames.AddFrame(image.Frames.RootFrame);
2272-
}
2249+
// mute image
2250+
images[i].Mutate(img => img.Resize(new ResizeOptions
2251+
{
2252+
Size = new Size(maxWidth, maxHeight),
2253+
Mode = ResizeMode.BoxPad,
2254+
PadColor = SixLabors.ImageSharp.Color.Transparent
2255+
}));
22732256

2274-
SetBinaryFromImageSharp(tiffImage);
2257+
// add frames to first image
2258+
_ = images[0].Frames.AddFrame(images[i].Frames.RootFrame);
22752259

2276-
foreach (Image image in images)
2277-
{
2278-
image.Dispose();
2260+
// dispose images past the first
2261+
images[i].Dispose();
22792262
}
2263+
2264+
// get raw binary
2265+
using var memoryStream = new MemoryStream();
2266+
images[0].Save(memoryStream, new TiffEncoder());
2267+
memoryStream.Seek(0, SeekOrigin.Begin);
2268+
2269+
// store result
2270+
Binary = memoryStream.ToArray();
2271+
Image?.Dispose();
2272+
Image = images[0];
22802273
}
22812274
catch (DllNotFoundException e)
22822275
{
@@ -2288,7 +2281,7 @@ private void OpenTiffToImageSharp(byte[] bytes)
22882281
}
22892282
}
22902283

2291-
private byte[] PrepareByteArray(Image<Rgba32> bmp, int[] raster, int width, int height)
2284+
private ReadOnlySpan<byte> PrepareByteArray(Image<Rgba32> bmp, int[] raster, int width, int height)
22922285
{
22932286
byte[] bits = new byte[GetStride(bmp) * height];
22942287

IronSoftware.Drawing/IronSoftware.Drawing.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "job_templates", "job_templa
4343
..\CI\job_templates\test_drawing_libraries.yml = ..\CI\job_templates\test_drawing_libraries.yml
4444
EndProjectSection
4545
EndProject
46+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IronSoftware.Drawing.Common.ConsoleTest", "IronSoftware.Drawing.Common.ConsoleTest\IronSoftware.Drawing.Common.ConsoleTest.csproj", "{A0B0F474-108B-4B60-834B-8FB169743687}"
47+
EndProject
4648
Global
4749
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4850
Debug|Any CPU = Debug|Any CPU
@@ -57,6 +59,10 @@ Global
5759
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
5860
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
5961
{B9418412-12AE-48D8-8B73-14DEB4FF47AD}.Release|Any CPU.Build.0 = Release|Any CPU
62+
{A0B0F474-108B-4B60-834B-8FB169743687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
63+
{A0B0F474-108B-4B60-834B-8FB169743687}.Debug|Any CPU.Build.0 = Debug|Any CPU
64+
{A0B0F474-108B-4B60-834B-8FB169743687}.Release|Any CPU.ActiveCfg = Release|Any CPU
65+
{A0B0F474-108B-4B60-834B-8FB169743687}.Release|Any CPU.Build.0 = Release|Any CPU
6066
EndGlobalSection
6167
GlobalSection(SolutionProperties) = preSolution
6268
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)