Skip to content

Commit e534614

Browse files
Merge pull request #110 from iron-software/releases/2024.7
Releases/2024.7 [master]
2 parents 984c891 + eb555ca commit e534614

8 files changed

Lines changed: 153 additions & 23 deletions

File tree

Binary file not shown.

IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/IronSoftware.Drawing.Common.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<PackageReference Include="FluentAssertions" Version="6.10.0" />
1414
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
1515
<PackageReference Include="Microsoft.Maui.Graphics" Version="7.0.92" />
16+
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="6.0.5.128" />
1617
<PackageReference Include="SkiaSharp" Version="2.88.7" />
1718
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
1819
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
@@ -29,6 +30,7 @@
2930

3031
<ItemGroup>
3132
<ProjectReference Include="..\IronSoftware.Drawing.Common\IronSoftware.Drawing.Common.csproj" />
33+
<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />
3234
</ItemGroup>
3335

3436
<ItemGroup>

IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/TestsBase.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,18 @@ protected static bool IsUnix()
6969
{
7070
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
7171
}
72+
73+
/// <summary>
74+
/// Get current processor architecture/bittiness string
75+
/// </summary>
76+
/// <returns>String representing architecture of the current process</returns>
77+
public static string GetArchitecture()
78+
{
79+
return RuntimeInformation.ProcessArchitecture == Architecture.Arm64
80+
? "arm64"
81+
: Environment.Is64BitProcess
82+
? "x64"
83+
: "x86";
84+
}
7285
}
7386
}

IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
using SixLabors.ImageSharp.Processing;
55
using System;
66
using System.Collections.Generic;
7+
using System.Drawing;
78
using System.Drawing.Imaging;
89
using System.IO;
910
using System.Linq;
1011
using Xunit;
1112
using Xunit.Abstractions;
13+
using Image = SixLabors.ImageSharp.Image;
1214

1315
namespace IronSoftware.Drawing.Common.Tests.UnitTests
1416
{
@@ -407,6 +409,19 @@ public void CastSixLabors_from_AnyBitmap()
407409
AssertImageAreEqual("expected.bmp", "result.bmp", true);
408410
}
409411

412+
[FactWithAutomaticDisplayName]
413+
public void CastBitmap_to_AnyBitmap_using_FromBitmap()
414+
{
415+
string imagePath = GetRelativeFilePath("van-gogh-starry-night-vincent-van-gogh.jpg");
416+
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(imagePath);
417+
AnyBitmap anyBitmap = AnyBitmap.FromBitmap(bitmap);
418+
419+
bitmap.Save("expected.png");
420+
anyBitmap.SaveAs("result.png");
421+
422+
AssertImageAreEqual("expected.png", "result.png", true);
423+
}
424+
410425
[FactWithAutomaticDisplayName]
411426
public void Load_Tiff_Image()
412427
{
@@ -463,11 +478,13 @@ public void Create_Multi_page_Tiff()
463478
[FactWithAutomaticDisplayName]
464479
public void Create_Multi_page_Tiff_Paths()
465480
{
481+
string outputImagePath = "create-tiff-output.tif";
466482
var imagePaths = new List<string>()
467483
{
468484
GetRelativeFilePath("first-animated-qr.png"),
469485
GetRelativeFilePath("last-animated-qr.png")
470486
};
487+
long maxInputFileSize = imagePaths.Select(path => new FileInfo(path).Length).Max();
471488

472489
var anyBitmap = AnyBitmap.CreateMultiFrameTiff(imagePaths);
473490
Assert.Equal(2, anyBitmap.FrameCount);
@@ -476,6 +493,13 @@ public void Create_Multi_page_Tiff_Paths()
476493
anyBitmap.GetAllFrames.ElementAt(1).SaveAs("last.png");
477494
AssertImageAreEqual(GetRelativeFilePath("first-animated-qr.png"), "first.png");
478495
AssertImageAreEqual(GetRelativeFilePath("last-animated-qr.png"), "last.png");
496+
497+
anyBitmap.SaveAs(outputImagePath);
498+
499+
long outputFileSize = new FileInfo(outputImagePath).Length;
500+
outputFileSize.Should().BeLessThanOrEqualTo(maxInputFileSize, $"Output file size ({outputFileSize}) exceeds the maximum input file size ({maxInputFileSize}).");
501+
502+
File.Delete(outputImagePath);
479503
}
480504

481505
[FactWithAutomaticDisplayName]
@@ -866,7 +890,7 @@ public void LoadImage_TiffImage_ShouldLoadWithoutThumbnail()
866890
bitmap.FrameCount.Should().Be(1);
867891
}
868892

869-
893+
#if !NET7_0
870894
[FactWithAutomaticDisplayName]
871895
public void CastAnyBitmap_from_SixLabors()
872896
{
@@ -881,6 +905,34 @@ public void CastAnyBitmap_from_SixLabors()
881905

882906
AssertLargeImageAreEqual("expected.bmp", "result.bmp", true);
883907
}
908+
#endif
909+
910+
911+
[IgnoreOnAzureDevopsX86Fact]
912+
public void Load_TiffImage_ShouldNotIncreaseFileSize()
913+
{
914+
// Arrange
915+
#if NET6_0_OR_GREATER
916+
double thresholdPercent = 0.15;
917+
#else
918+
double thresholdPercent = 1.5;
919+
#endif
920+
string imagePath = GetRelativeFilePath("test_dw_10.tif");
921+
string outputImagePath = "output.tif";
922+
923+
// Act
924+
var bitmap = new AnyBitmap(imagePath);
925+
bitmap.SaveAs(outputImagePath);
926+
var originalFileSize = new FileInfo(imagePath).Length;
927+
var maxAllowedFileSize = (long)(originalFileSize * (1 + thresholdPercent));
928+
var outputFileSize = new FileInfo(outputImagePath).Length;
929+
930+
// Assert
931+
outputFileSize.Should().BeLessThanOrEqualTo(maxAllowedFileSize);
932+
933+
// Clean up
934+
File.Delete(outputImagePath);
935+
}
884936

885937
}
886938
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using IronSoftware.Drawing.Common.Tests;
3+
using Xunit;
4+
5+
public sealed class IgnoreOnAzureDevopsX86FactAttribute : FactWithAutomaticDisplayNameAttribute
6+
{
7+
/// <summary>
8+
/// This class using for Skip test on Azure Devops and x86 architect
9+
/// <para>Regarding to OcrWizardTests.RunningWizardShouldFindBetterResult always failed on Devops x86 tested</para>
10+
/// </summary>
11+
public IgnoreOnAzureDevopsX86FactAttribute()
12+
{
13+
if (!(IsRunningOnAzureDevOps() && IsRunningOnX86()))
14+
{
15+
return;
16+
}
17+
18+
Skip = "Ignored on Azure DevOps";
19+
}
20+
21+
/// <summary>Determine if runtime is Azure DevOps.</summary>
22+
/// <returns>True if being executed in Azure DevOps, false otherwise.</returns>
23+
public static bool IsRunningOnAzureDevOps()
24+
{
25+
return Environment.GetEnvironmentVariable("SYSTEM_DEFINITIONID") != null;
26+
}
27+
28+
// <summary>Determine if runtime is x86 architect.</summary>
29+
/// <returns>True if being executed in x86 architect, false otherwise.</returns>
30+
public static bool IsRunningOnX86()
31+
{
32+
return TestsBase.GetArchitecture() == "x86";
33+
}
34+
}

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

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using SixLabors.ImageSharp.Formats.Jpeg;
99
using SixLabors.ImageSharp.Formats.Png;
1010
using SixLabors.ImageSharp.Formats.Tiff;
11+
using SixLabors.ImageSharp.Formats.Tiff.Constants;
1112
using SixLabors.ImageSharp.Formats.Webp;
1213
using SixLabors.ImageSharp.PixelFormats;
1314
using SixLabors.ImageSharp.Processing;
@@ -21,7 +22,6 @@
2122
using System.Net.Http;
2223
using System.Runtime.CompilerServices;
2324
using System.Runtime.InteropServices;
24-
using System.Runtime.InteropServices.ComTypes;
2525
using System.Threading.Tasks;
2626

2727
namespace IronSoftware.Drawing
@@ -50,6 +50,7 @@ public partial class AnyBitmap : IDisposable
5050
private Image Image { get; set; }
5151
private byte[] Binary { get; set; }
5252
private IImageFormat Format { get; set; }
53+
private TiffCompression TiffCompression { get; set; } = TiffCompression.Lzw;
5354

5455
/// <summary>
5556
/// Width of the image.
@@ -292,7 +293,10 @@ public void ExportStream(
292293
ImageFormat.Gif => new GifEncoder(),
293294
ImageFormat.Png => new PngEncoder(),
294295
ImageFormat.Webp => new WebpEncoder() { Quality = lossy },
295-
ImageFormat.Tiff => new TiffEncoder(),
296+
ImageFormat.Tiff => new TiffEncoder()
297+
{
298+
Compression = TiffCompression
299+
},
296300
_ => new BmpEncoder()
297301
{
298302
BitsPerPixel = BmpBitsPerPixel.Pixel32,
@@ -410,10 +414,10 @@ public static AnyBitmap FromBitmap<T>(T otherBitmapFormat)
410414
{
411415
try
412416
{
413-
var result = (AnyBitmap)Convert.ChangeType(
417+
var bitmap = (System.Drawing.Bitmap)Convert.ChangeType(
414418
otherBitmapFormat,
415-
typeof(AnyBitmap));
416-
return result;
419+
typeof(System.Drawing.Bitmap));
420+
return (AnyBitmap)bitmap;
417421
}
418422
catch (Exception e)
419423
{
@@ -1321,7 +1325,7 @@ public static implicit operator Image(AnyBitmap bitmap)
13211325
{
13221326
try
13231327
{
1324-
return Image.Load<Rgba32>(bitmap.Binary);
1328+
return Image.Load(bitmap.Binary);
13251329
}
13261330
catch (DllNotFoundException e)
13271331
{
@@ -2203,23 +2207,25 @@ private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
22032207
using MemoryStream tiffStream = new(bytes.ToArray());
22042208

22052209
// open a TIFF stored in the stream
2206-
using (Tiff tif = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
2210+
using (Tiff tiff = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
22072211
{
2208-
short num = tif.NumberOfDirectories();
2212+
SetTiffCompression(tiff);
2213+
2214+
short num = tiff.NumberOfDirectories();
22092215
for (short i = 0; i < num; i++)
22102216
{
2211-
_ = tif.SetDirectory(i);
2217+
_ = tiff.SetDirectory(i);
22122218

2213-
if (IsThumbnail(tif))
2219+
if (IsThumbnail(tiff))
22142220
{
22152221
continue;
22162222
}
22172223

2218-
var (width, height) = SetWidthHeight(tif, i, ref imageWidth, ref imageHeight);
2224+
var (width, height) = SetWidthHeight(tiff, i, ref imageWidth, ref imageHeight);
22192225

22202226
// Read the image into the memory buffer
22212227
int[] raster = new int[height * width];
2222-
if (!tif.ReadRGBAImage(width, height, raster))
2228+
if (!tiff.ReadRGBAImage(width, height, raster))
22232229
{
22242230
throw new NotSupportedException("Could not read image");
22252231
}
@@ -2231,7 +2237,7 @@ private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
22312237
images.Add(Image.LoadPixelData<Rgba32>(bits, bmp.Width, bmp.Height));
22322238
}
22332239
}
2234-
2240+
22352241
// find max
22362242
FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight);
22372243

@@ -2259,7 +2265,7 @@ private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
22592265

22602266
// dispose images past the first
22612267
images[i].Dispose();
2262-
}
2268+
}
22632269

22642270
// get raw binary
22652271
using var memoryStream = new MemoryStream();
@@ -2281,14 +2287,35 @@ private void OpenTiffToImageSharp(ReadOnlySpan<byte> bytes)
22812287
}
22822288
}
22832289

2290+
private void SetTiffCompression(Tiff tiff)
2291+
{
2292+
Compression tiffCompression = tiff.GetField(TiffTag.COMPRESSION) != null && tiff.GetField(TiffTag.COMPRESSION).Length > 0
2293+
? (Compression)tiff.GetField(TiffTag.COMPRESSION)[0].ToInt()
2294+
: Compression.NONE;
2295+
2296+
TiffCompression = tiffCompression switch
2297+
{
2298+
Compression.CCITTRLE => TiffCompression.Ccitt1D,
2299+
Compression.CCITTFAX3 => TiffCompression.CcittGroup3Fax,
2300+
Compression.CCITTFAX4 => TiffCompression.CcittGroup4Fax,
2301+
Compression.JPEG => TiffCompression.Jpeg,
2302+
Compression.OJPEG => TiffCompression.OldJpeg,
2303+
Compression.NEXT => TiffCompression.NeXT,
2304+
Compression.PACKBITS => TiffCompression.PackBits,
2305+
Compression.THUNDERSCAN => TiffCompression.ThunderScan,
2306+
Compression.DEFLATE => TiffCompression.Deflate,
2307+
_ => TiffCompression.Lzw
2308+
};
2309+
}
2310+
22842311
/// <summary>
22852312
/// Determines if a TIFF frame contains a thumbnail.
22862313
/// </summary>
2287-
/// <param name="tif">The <see cref="Tiff"/> which set number of directory to analyze.</param>
2314+
/// <param name="tiff">The <see cref="Tiff"/> which set number of directory to analyze.</param>
22882315
/// <returns>True if the frame contains a thumbnail, otherwise false.</returns>
2289-
private bool IsThumbnail(Tiff tif)
2316+
private bool IsThumbnail(Tiff tiff)
22902317
{
2291-
FieldValue[] subFileTypeFieldValue = tif.GetField(TiffTag.SUBFILETYPE);
2318+
FieldValue[] subFileTypeFieldValue = tiff.GetField(TiffTag.SUBFILETYPE);
22922319

22932320
// Current thumbnail identification relies on the SUBFILETYPE tag with a value of FileType.REDUCEDIMAGE.
22942321
// This may need refinement in the future to include additional checks
@@ -2319,13 +2346,13 @@ private ReadOnlySpan<byte> PrepareByteArray(Image<Rgba32> bmp, int[] raster, int
23192346
return bits;
23202347
}
23212348

2322-
private (int width, int height) SetWidthHeight(Tiff tif, short index, ref int imageWidth, ref int imageHeight)
2349+
private (int width, int height) SetWidthHeight(Tiff tiff, short index, ref int imageWidth, ref int imageHeight)
23232350
{
23242351
// Find the width and height of the image
2325-
FieldValue[] value = tif.GetField(TiffTag.IMAGEWIDTH);
2352+
FieldValue[] value = tiff.GetField(TiffTag.IMAGEWIDTH);
23262353
int width = value[0].ToInt();
23272354

2328-
value = tif.GetField(TiffTag.IMAGELENGTH);
2355+
value = tiff.GetField(TiffTag.IMAGELENGTH);
23292356
int height = value[0].ToInt();
23302357

23312358
if (index == 0)

IronSoftware.Drawing/IronSoftware.Drawing.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ 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}"
46+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IronSoftware.Drawing.Common.ConsoleTest", "IronSoftware.Drawing.Common.ConsoleTest\IronSoftware.Drawing.Common.ConsoleTest.csproj", "{A0B0F474-108B-4B60-834B-8FB169743687}"
4747
EndProject
4848
Global
4949
GlobalSection(SolutionConfigurationPlatforms) = preSolution

NuGet/IronSoftware.Drawing.nuspec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ Supports:
3939

4040
For general support and technical inquiries, please email us at: support@ironsoftware.com</description>
4141
<summary>IronSoftware.System.Drawing is an open-source solution for .NET developers to replace System.Drawing.Common with a universal and flexible library.</summary>
42-
<releaseNotes>- Improves memory management when loading large image input</releaseNotes>
42+
<releaseNotes>- Efficient compression significantly reduces output TIFF file size (Up to 80%).
43+
- Automatic compression when creating multi-frame TIFFs.
44+
- Fix the issue of exception error thrown when converting Bitmap to AnyBitmap.</releaseNotes>
4345
<copyright>Copyright © Iron Software 2022-2024</copyright>
4446
<tags>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</tags>
4547
<repository type="git" url="https://github.com/iron-software/IronSoftware.Drawing.Common" commit="$commit$" />

0 commit comments

Comments
 (0)