diff --git a/.gitignore b/.gitignore index dfcfd56..d4f75d6 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,10 @@ ipch/ # Visual Studio Trace Files *.e2e +# JetBrains IDE settings (e.g., IntelliJ, Rider, PyCharm) +.idea/ + + # TFS 2012 Local Workspace $tf/ diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs index 83f30a5..18569bc 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/AnyBitmapFunctionality.cs @@ -26,7 +26,14 @@ public void Create_AnyBitmap_by_Filename() string imagePath = GetRelativeFilePath("Mona-Lisa-oil-wood-panel-Leonardo-da.webp"); var bitmap = AnyBitmap.FromFile(imagePath); + bitmap.IsImageLoaded().Should().BeFalse(); + bitmap.SaveAs("result.bmp"); + + bitmap.IsImageLoaded().Should().BeTrue(); + //should still be the original bytes + bitmap.Length.Should().Be((int)new FileInfo(imagePath).Length); + Assert.Equal(671, bitmap.Width); Assert.Equal(1000, bitmap.Height); Assert.Equal(74684, bitmap.Length); @@ -47,7 +54,14 @@ public void Create_AnyBitmap_by_Byte() byte[] bytes = File.ReadAllBytes(imagePath); var bitmap = AnyBitmap.FromBytes(bytes); + bitmap.IsImageLoaded().Should().BeFalse(); + _ = bitmap.TrySaveAs("result.bmp"); + + bitmap.IsImageLoaded().Should().BeTrue(); + //should still be the original bytes + bitmap.Length.Should().Be(bytes.Length); + AssertImageAreEqual(imagePath, "result.bmp"); bitmap = new AnyBitmap(bytes); @@ -63,7 +77,14 @@ public void Create_AnyBitmap_by_Stream() Stream ms = new MemoryStream(bytes); var bitmap = AnyBitmap.FromStream(ms); + bitmap.IsImageLoaded().Should().BeFalse(); + _ = bitmap.TrySaveAs("result.bmp"); + + bitmap.IsImageLoaded().Should().BeTrue(); + //should still be the original bytes + bitmap.Length.Should().Be(bytes.Length); + AssertImageAreEqual(imagePath, "result.bmp"); ms.Position = 0; @@ -80,12 +101,21 @@ public void Create_AnyBitmap_by_MemoryStream() var ms = new MemoryStream(bytes); var bitmap = AnyBitmap.FromStream(ms); + bitmap.IsImageLoaded().Should().BeFalse(); + _ = bitmap.TrySaveAs("result.bmp"); + + bitmap.IsImageLoaded().Should().BeTrue(); + //should still be the original bytes + bitmap.Length.Should().Be(bytes.Length); + AssertImageAreEqual(imagePath, "result.bmp"); bitmap = new AnyBitmap(ms); bitmap.SaveAs("result.bmp"); AssertImageAreEqual(imagePath, "result.bmp"); + + } [FactWithAutomaticDisplayName] @@ -162,7 +192,7 @@ public void CastBitmap_to_AnyBitmap() bitmap.Save("expected.bmp"); anyBitmap.SaveAs("result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.bmp", "result.bmp", true); } @@ -187,7 +217,7 @@ public void CastImage_to_AnyBitmap() bitmap.Save("expected.bmp"); anyBitmap.SaveAs("result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.bmp", "result.bmp", true); } @@ -245,6 +275,16 @@ public void AnyBitmap_should_set_Pixel() // Check the pixel color has changed Assert.Equal(bitmap.GetPixel(0, 0), Color.Black); + +#if NETFRAMEWORK + //windows only + // SetPixel makes the image dirty so it should update AnyBitmap.Binary value + + System.Drawing.Bitmap temp1 = bitmap; + AnyBitmap temp2 = (AnyBitmap)temp1; + Assert.Equal(temp1.GetPixel(0, 0).ToArgb(), System.Drawing.Color.Black.ToArgb()); + Assert.Equal(temp2.GetPixel(0, 0), Color.Black); +#endif } } @@ -348,6 +388,22 @@ public void Clone_AnyBitmap() AssertImageAreEqual("expected.png", "result.png", true); } + [FactWithAutomaticDisplayName] + public void Clone_Crop_AnyBitmap() + { + string imagePath = GetRelativeFilePath("van-gogh-starry-night-vincent-van-gogh.jpg"); + var anyBitmap = AnyBitmap.FromFile(imagePath); + AnyBitmap clonedAnyBitmap = anyBitmap.Clone(new Rectangle(100,100,100,100)); + + clonedAnyBitmap.Width.Should().Be(100); + clonedAnyBitmap.Height.Should().Be(100); + + var recheckClonedAnyBitmap = AnyBitmap.FromBytes(clonedAnyBitmap.GetBytes()); + + recheckClonedAnyBitmap.Width.Should().Be(100); + recheckClonedAnyBitmap.Width.Should().Be(100); + } + [FactWithAutomaticDisplayName] public void CastSKBitmap_to_AnyBitmap() { @@ -357,7 +413,7 @@ public void CastSKBitmap_to_AnyBitmap() SaveSkiaBitmap(skBitmap, "expected.png"); anyBitmap.SaveAs("result.png"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.png", "result.png", true); } @@ -390,7 +446,7 @@ public void CastSKImage_to_AnyBitmap() SaveSkiaImage(skImage, "expected.png"); anyBitmap.SaveAs("result.png"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.png", "result.png", true); } @@ -423,14 +479,18 @@ public void CastSixLabors_to_AnyBitmap() imgSharp.Save("expected.bmp"); anyBitmap.SaveAs("result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.bmp", "result.bmp", true); } - [FactWithAutomaticDisplayName] - public void CastSixLabors_from_AnyBitmap() + [TheoryWithAutomaticDisplayName] + [InlineData("mountainclimbers.jpg")] + [InlineData("van-gogh-starry-night-vincent-van-gogh.jpg")] + [InlineData("animated_qr.gif")] + [InlineData("Sample-Tiff-File-download-for-Testing.tiff")] + public void CastSixLabors_from_AnyBitmap(string filename) { - var anyBitmap = AnyBitmap.FromFile(GetRelativeFilePath("mountainclimbers.jpg")); + var anyBitmap = AnyBitmap.FromFile(GetRelativeFilePath(filename)); Image imgSharp = anyBitmap; anyBitmap.SaveAs("expected.bmp"); @@ -439,6 +499,38 @@ public void CastSixLabors_from_AnyBitmap() AssertImageAreEqual("expected.bmp", "result.bmp", true); } + [TheoryWithAutomaticDisplayName] + [InlineData("mountainclimbers.jpg")] + [InlineData("van-gogh-starry-night-vincent-van-gogh.jpg")] + [InlineData("animated_qr.gif")] + [InlineData("Sample-Tiff-File-download-for-Testing.tiff")] + public void CastSixLabors_from_AnyBitmap_Rgb24(string filename) + { + var anyBitmap = AnyBitmap.FromFile(GetRelativeFilePath(filename)); + Image imgSharp = anyBitmap; + + anyBitmap.SaveAs("expected.bmp"); + imgSharp.Save("result.bmp"); + + AssertImageAreEqual("expected.bmp", "result.bmp", true); + } + + [TheoryWithAutomaticDisplayName] + [InlineData("mountainclimbers.jpg")] + [InlineData("van-gogh-starry-night-vincent-van-gogh.jpg")] + [InlineData("animated_qr.gif")] + [InlineData("Sample-Tiff-File-download-for-Testing.tiff")] + public void CastSixLabors_from_AnyBitmap_Rgba32(string filename) + { + var anyBitmap = AnyBitmap.FromFile(GetRelativeFilePath(filename)); + Image imgSharp = anyBitmap; + + anyBitmap.SaveAs("expected.bmp"); + imgSharp.Save("result.bmp"); + + AssertImageAreEqual("expected.bmp", "result.bmp", true); + } + [FactWithAutomaticDisplayName] public void CastBitmap_to_AnyBitmap_using_FromBitmap() { @@ -448,7 +540,7 @@ public void CastBitmap_to_AnyBitmap_using_FromBitmap() bitmap.Save("expected.png"); anyBitmap.SaveAs("result.png"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.png", "result.png", true); } @@ -679,6 +771,7 @@ public void Should_Resize_Image(string fileName, int width, int height) var resizeAnyBitmap = new AnyBitmap(anyBitmap, width, height); _ = resizeAnyBitmap.Width.Should().Be(width); _ = resizeAnyBitmap.Height.Should().Be(height); + resizeAnyBitmap.GetPixel(0, 0); //should not throw error } [FactWithAutomaticDisplayName] @@ -758,6 +851,25 @@ public void TestGetRGBBuffer() Assert.Equal(firstPixel.B, buffer[2]); } + //[FactWithAutomaticDisplayName] + public void TestGetRGBABuffer() + { + string imagePath = GetRelativeFilePath("checkmark.jpg"); + using var bitmap = new AnyBitmap(imagePath); + var expectedSize = bitmap.Width * bitmap.Height * 4; // 4 bytes per pixel (RGB) + + byte[] buffer = bitmap.GetRGBABuffer(); + + Assert.Equal(expectedSize, buffer.Length); + + // Verify the first pixel's RGB values + var firstPixel = bitmap.GetPixel(0, 0); + Assert.Equal(firstPixel.R, buffer[0]); + Assert.Equal(firstPixel.G, buffer[1]); + Assert.Equal(firstPixel.B, buffer[2]); + Assert.Equal(firstPixel.A, buffer[3]); + } + [FactWithAutomaticDisplayName] public void Test_LoadFromRGBBuffer() { @@ -822,10 +934,11 @@ public void AnyBitmapShouldReturnCorrectResolutions(string fileName, double expe { string imagePath = GetRelativeFilePath(fileName); var bitmap = AnyBitmap.FromFile(imagePath); + var frames = bitmap.GetAllFrames; for (int i = 0; i < bitmap.FrameCount; i++) { - Assert.Equal(expectedHorizontalResolution, bitmap.GetAllFrames.ElementAt(i).HorizontalResolution); - Assert.Equal(expectedVerticalResolution, bitmap.GetAllFrames.ElementAt(i).VerticalResolution); + Assert.Equal(expectedHorizontalResolution, frames.ElementAt(i).HorizontalResolution.Value, 1d); + Assert.Equal(expectedVerticalResolution, frames.ElementAt(i).VerticalResolution.Value, 1d); } } @@ -840,7 +953,7 @@ public void CastMaui_to_AnyBitmap() SaveMauiImages(image, "expected.bmp"); anyBitmap.SaveAs("result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.bmp", "result.bmp", true); } @@ -852,7 +965,7 @@ public void CastMaui_from_AnyBitmap() anyBitmap.SaveAs("expected.bmp"); SaveMauiImages(image, "result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertImageAreEqual("expected.bmp", "result.bmp", true); } #endif @@ -876,6 +989,7 @@ public void Create_New_Image_Instance() blankBitmap.Width.Should().Be(8); blankBitmap.Height.Should().Be(8); + blankBitmap.GetPixel(0, 0); //should not throw error } [FactWithAutomaticDisplayName] @@ -974,36 +1088,101 @@ public void CastAnyBitmap_from_SixLabors() image.Save("expected.bmp"); anyBitmap.SaveAs("result.bmp"); - + anyBitmap.GetPixel(0, 0); //should not throw error AssertLargeImageAreEqual("expected.bmp", "result.bmp", true); } #endif - [IgnoreOnAzureDevopsX86Fact] - public void Load_TiffImage_ShouldNotIncreaseFileSize() + [IgnoreOnAzureDevopsX86Fact] + public void Load_TiffImage_ShouldNotIncreaseFileSize() + { + // Arrange + #if NET6_0_OR_GREATER + double thresholdPercent = 0.15; + #else + double thresholdPercent = 1.5; + #endif + string imagePath = GetRelativeFilePath("test_dw_10.tif"); + string outputImagePath = "output.tif"; + + // Act + var bitmap = new AnyBitmap(imagePath); + bitmap.SaveAs(outputImagePath); + var originalFileSize = new FileInfo(imagePath).Length; + var maxAllowedFileSize = (long)(originalFileSize * (1 + thresholdPercent)); + var outputFileSize = new FileInfo(outputImagePath).Length; + + // Assert + outputFileSize.Should().BeLessThanOrEqualTo(maxAllowedFileSize); + + // Clean up + File.Delete(outputImagePath); + } + + [Theory] + [InlineData("DW-26 MultiPageTif120Input.tiff")] + [InlineData("google_large_1500dpi.bmp")] + public void DW_34_ShouldNotThrowOutOfMemory(string filename) { - // Arrange -#if NET6_0_OR_GREATER - double thresholdPercent = 0.15; -#else - double thresholdPercent = 1.5; -#endif - string imagePath = GetRelativeFilePath("test_dw_10.tif"); - string outputImagePath = "output.tif"; + string imagePath = GetRelativeFilePath(filename); - // Act - var bitmap = new AnyBitmap(imagePath); - bitmap.SaveAs(outputImagePath); - var originalFileSize = new FileInfo(imagePath).Length; - var maxAllowedFileSize = (long)(originalFileSize * (1 + thresholdPercent)); - var outputFileSize = new FileInfo(outputImagePath).Length; + List images = new List(); + for (int i = 0; i < 25; i++) + { + var bitmap = new AnyBitmap(imagePath); + images.Add(bitmap); + bitmap.IsImageLoaded().Should().BeFalse(); + } - // Assert - outputFileSize.Should().BeLessThanOrEqualTo(maxAllowedFileSize); + images.ForEach(bitmap => bitmap.Dispose()); + } - // Clean up - File.Delete(outputImagePath); + //[Fact] + //public void LoadTiff() + //{ + // Stopwatch stopWatch = new Stopwatch(); + // stopWatch.Start(); + // for (int i = 0; i < 25; i++) + // { + // var bitmap = new AnyBitmap("C:\\repo\\IronInternalBenchmarks\\IronOcrBenchmark\\Images\\001_20221121000002_S2123457_EL37.tiff"); + // //var c = bitmap.GetPixel(10,10); + // foreach (var item in bitmap.GetAllFrames) + // { + // item.GetRGBBuffer(); + // item.ExtractAlphaData(); + // } + + + // } + // stopWatch.Stop(); + // // Get the elapsed time as a TimeSpan value. + // TimeSpan ts = stopWatch.Elapsed; + // ts.Should().Be(TimeSpan.FromHours(1)); + //} + + // [FactWithAutomaticDisplayName] + public void AnyBitmap_ExportGif_Should_Works() + { + string imagePath = GetRelativeFilePath("van-gogh-starry-night-vincent-van-gogh.jpg"); + var anyBitmap = AnyBitmap.FromFile(imagePath); + + using var resultExport = new MemoryStream(); + anyBitmap.ExportStream(resultExport, AnyBitmap.ImageFormat.Gif); + resultExport.Length.Should().NotBe(0); + Image.DetectFormat(resultExport.ToArray()).Should().Be(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance); + } + + // [FactWithAutomaticDisplayName] + public void AnyBitmap_ExportTiff_Should_Works() + { + string imagePath = GetRelativeFilePath("van-gogh-starry-night-vincent-van-gogh.jpg"); + var anyBitmap = AnyBitmap.FromFile(imagePath); + + using var resultExport = new MemoryStream(); + anyBitmap.ExportStream(resultExport, AnyBitmap.ImageFormat.Tiff); + resultExport.Length.Should().NotBe(0); + Image.DetectFormat(resultExport.ToArray()).Should().Be(SixLabors.ImageSharp.Formats.Tiff.TiffFormat.Instance); } } diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/ColorFunctionality.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/ColorFunctionality.cs index abe8e17..41c1c7a 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/ColorFunctionality.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common.Tests/UnitTests/ColorFunctionality.cs @@ -19,18 +19,21 @@ public void Create_new_Color() Assert.Equal(25, color.R); Assert.Equal(25, color.G); Assert.Equal(25, color.B); + Assert.False(color.IsKnownColor); color = new Color("#800080"); Assert.Equal(255, color.A); Assert.Equal(128, color.R); Assert.Equal(0, color.G); Assert.Equal(128, color.B); + Assert.False(color.IsKnownColor); color = new Color("#F0F"); Assert.Equal(255, color.A); Assert.Equal(255, color.R); Assert.Equal(0, color.G); Assert.Equal(255, color.B); + Assert.False(color.IsKnownColor); InvalidOperationException ex = Assert.Throws(() => color = new Color("#F")); Assert.Equal($"#F is unable to convert to {typeof(Color)} because it requires a suitable length of string.", ex.Message); @@ -40,12 +43,14 @@ public void Create_new_Color() Assert.Equal(255, color.R); Assert.Equal(0, color.G); Assert.Equal(255, color.B); + Assert.False(color.IsKnownColor); color = new Color(255, 0, 255); Assert.Equal(255, color.A); Assert.Equal(255, color.R); Assert.Equal(0, color.G); Assert.Equal(255, color.B); + Assert.False(color.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -55,21 +60,25 @@ public void Create_correct_color_from_system_defined() Assert.Equal(154, Color.YellowGreen.R); Assert.Equal(205, Color.YellowGreen.G); Assert.Equal(50, Color.YellowGreen.B); + Assert.True(Color.YellowGreen.IsKnownColor); Assert.Equal(255, Color.Violet.A); Assert.Equal(238, Color.Violet.R); Assert.Equal(130, Color.Violet.G); Assert.Equal(238, Color.Violet.B); + Assert.True(Color.Violet.IsKnownColor); Assert.Equal(0, Color.Transparent.A); Assert.Equal(255, Color.Transparent.R); Assert.Equal(255, Color.Transparent.G); Assert.Equal(255, Color.Transparent.B); + Assert.True(Color.Transparent.IsKnownColor); Assert.Equal(255, Color.Azure.A); Assert.Equal(240, Color.Azure.R); Assert.Equal(255, Color.Azure.G); Assert.Equal(255, Color.Azure.B); + Assert.True(Color.Azure.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -82,6 +91,7 @@ public void Create_color_from_RGB() Assert.Equal(244, color.G); Assert.Equal(208, color.B); Assert.Equal("#FF40F4D0", color.ToString()); + Assert.False(color.IsKnownColor); color = Color.FromArgb(0, 64, 244, 208); @@ -90,7 +100,7 @@ public void Create_color_from_RGB() Assert.Equal(244, color.G); Assert.Equal(208, color.B); Assert.Equal("#0040F4D0", color.ToString()); - + Assert.False(color.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -103,12 +113,14 @@ public void Create_color_from_ARGB() Assert.Equal(244, color.G); Assert.Equal(208, color.B); Assert.Equal("#6440F4D0", color.ToString()); + Assert.False(color.IsKnownColor); var color1 = Color.FromArgb(50, color); Assert.Equal(50, color1.A); Assert.Equal(64, color1.R); Assert.Equal(244, color1.G); Assert.Equal(208, color1.B); + Assert.False(color.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -116,12 +128,15 @@ public void Get_Luminance_from_color() { Color color = Color.Black; Assert.Equal(0, color.GetLuminance()); + Assert.True(color.IsKnownColor); color = Color.Gray; Assert.Equal(50, color.GetLuminance()); + Assert.True(color.IsKnownColor); color = Color.White; Assert.Equal(100, color.GetLuminance()); + Assert.True(color.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -133,6 +148,7 @@ public void Cast_System_Drawing_Color_from_Color() Assert.Equal(255, red.R); Assert.Equal(0, red.G); Assert.Equal(0, red.B); + Assert.False(red.IsKnownColor); drawingColor = System.Drawing.Color.FromArgb(255, 0, 255, 0); Color green = drawingColor; @@ -140,6 +156,7 @@ public void Cast_System_Drawing_Color_from_Color() Assert.Equal(0, green.R); Assert.Equal(255, green.G); Assert.Equal(0, green.B); + Assert.False(green.IsKnownColor); drawingColor = System.Drawing.Color.FromArgb(0, 0, 255); Color blue = drawingColor; @@ -147,6 +164,7 @@ public void Cast_System_Drawing_Color_from_Color() Assert.Equal(0, blue.R); Assert.Equal(0, blue.G); Assert.Equal(255, blue.B); + Assert.False(blue.IsKnownColor); int iColorCode = Convert.ToInt32("1e81b0", 16); drawingColor = System.Drawing.Color.FromArgb(iColorCode); @@ -155,6 +173,7 @@ public void Cast_System_Drawing_Color_from_Color() Assert.Equal(30, color.R); Assert.Equal(129, color.G); Assert.Equal(176, color.B); + Assert.False(color.IsKnownColor); } [FactWithAutomaticDisplayName] @@ -399,20 +418,20 @@ public void Cast_ImageSharp_Rgb24_to_Color() [FactWithAutomaticDisplayName] public void Cast_ImageSharp_Rgb48_from_Color() { - var imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(255, 0, 0); + var imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(65535, 0, 0); Color red = imgColor; Assert.Equal(255, red.R); Assert.Equal(0, red.G); Assert.Equal(0, red.B); - imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(0, 255, 0); + imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(0, 65535, 0); Color green = imgColor; Assert.Equal(255, green.A); Assert.Equal(0, green.R); Assert.Equal(255, green.G); Assert.Equal(0, green.B); - imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(0, 0, 255); + imgColor = new SixLabors.ImageSharp.PixelFormats.Rgb48(0, 0, 65535); Color blue = imgColor; Assert.Equal(0, blue.R); Assert.Equal(0, blue.G); @@ -423,28 +442,29 @@ public void Cast_ImageSharp_Rgb48_from_Color() public void Cast_ImageSharp_Rgb48_to_Color() { Color color = Color.Red; + //Rgb42 is 16-bit color (0-65535) not (0-255) SixLabors.ImageSharp.PixelFormats.Rgb48 red = color; - Assert.Equal(255, red.R); + Assert.Equal(65535, red.R); Assert.Equal(0, red.G); Assert.Equal(0, red.B); color = new Color(0, 255, 0); SixLabors.ImageSharp.PixelFormats.Rgb48 green = color; Assert.Equal(0, green.R); - Assert.Equal(255, green.G); + Assert.Equal(65535, green.G); Assert.Equal(0, green.B); color = new Color("#0000FF"); SixLabors.ImageSharp.PixelFormats.Rgb48 blue = color; Assert.Equal(0, blue.R); Assert.Equal(0, blue.G); - Assert.Equal(255, blue.B); + Assert.Equal(65535, blue.B); color = Color.FromArgb(Convert.ToInt32("1e81b0", 16)); SixLabors.ImageSharp.PixelFormats.Rgb48 imgColor = color; - Assert.Equal(30, imgColor.R); - Assert.Equal(129, imgColor.G); - Assert.Equal(176, imgColor.B); + Assert.Equal(7710, imgColor.R); + Assert.Equal(33153, imgColor.G); + Assert.Equal(45232, imgColor.B); } [FactWithAutomaticDisplayName] @@ -456,14 +476,14 @@ public void Cast_ImageSharp_Rgba64_from_Color() Assert.Equal(0, red.G); Assert.Equal(0, red.B); - imgColor = new SixLabors.ImageSharp.PixelFormats.Rgba64(0, 255, 0, 255); + imgColor = new SixLabors.ImageSharp.PixelFormats.Rgba64(0, 65535, 0, 65535); Color green = imgColor; Assert.Equal(255, green.A); Assert.Equal(0, green.R); Assert.Equal(255, green.G); Assert.Equal(0, green.B); - imgColor = new SixLabors.ImageSharp.PixelFormats.Rgba64(0, 0, 255, 255); + imgColor = new SixLabors.ImageSharp.PixelFormats.Rgba64(0, 0, 65535, 65535); Color blue = imgColor; Assert.Equal(255, green.A); Assert.Equal(0, blue.R); @@ -525,46 +545,69 @@ public void Should_Create_FromName() _ = color.R.Should().Be(255); _ = color.G.Should().Be(0); _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("green"); _ = color.R.Should().Be(0); _ = color.G.Should().Be(128); _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("blue"); _ = color.R.Should().Be(0); _ = color.G.Should().Be(0); _ = color.B.Should().Be(255); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("yellow"); _ = color.R.Should().Be(255); _ = color.G.Should().Be(255); _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("pink"); _ = color.R.Should().Be(255); _ = color.G.Should().Be(192); _ = color.B.Should().Be(203); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("brown"); _ = color.R.Should().Be(165); _ = color.G.Should().Be(42); _ = color.B.Should().Be(42); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("gray"); _ = color.R.Should().Be(128); _ = color.G.Should().Be(128); _ = color.B.Should().Be(128); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("black"); _ = color.R.Should().Be(0); _ = color.G.Should().Be(0); _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeTrue(); color = Color.FromName("orange"); _ = color.R.Should().Be(255); _ = color.G.Should().Be(165); _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeTrue(); + + color = Color.FromName("RebeccaPurple"); + _ = color.A.Should().Be(255); + _ = color.R.Should().Be(102); + _ = color.G.Should().Be(51); + _ = color.B.Should().Be(153); + _ = color.IsKnownColor.Should().BeTrue(); + + color = Color.FromName("NotAColor"); + _ = color.A.Should().Be(0); + _ = color.R.Should().Be(0); + _ = color.G.Should().Be(0); + _ = color.B.Should().Be(0); + _ = color.IsKnownColor.Should().BeFalse(); } [FactWithAutomaticDisplayName] diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.Enum.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.Enum.cs new file mode 100644 index 0000000..bc67012 --- /dev/null +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.Enum.cs @@ -0,0 +1,213 @@ +using System; +using System.IO; + +namespace IronSoftware.Drawing +{ +public partial class AnyBitmap + { + #pragma warning disable CS0618 + /// + /// Converts the legacy to and + /// + [Obsolete("RotateFlipType is legacy support from System.Drawing. " + + "Please use RotateMode and FlipMode instead.")] + internal static (RotateMode, FlipMode) ParseRotateFlipType(RotateFlipType rotateFlipType) + { + return rotateFlipType switch + { + RotateFlipType.RotateNoneFlipNone or RotateFlipType.Rotate180FlipXY => (RotateMode.None, FlipMode.None), + RotateFlipType.Rotate90FlipNone or RotateFlipType.Rotate270FlipXY => (RotateMode.Rotate90, FlipMode.None), + RotateFlipType.RotateNoneFlipXY or RotateFlipType.Rotate180FlipNone => (RotateMode.Rotate180, FlipMode.None), + RotateFlipType.Rotate90FlipXY or RotateFlipType.Rotate270FlipNone => (RotateMode.Rotate270, FlipMode.None), + RotateFlipType.RotateNoneFlipX or RotateFlipType.Rotate180FlipY => (RotateMode.None, FlipMode.Horizontal), + RotateFlipType.Rotate90FlipX or RotateFlipType.Rotate270FlipY => (RotateMode.Rotate90, FlipMode.Horizontal), + RotateFlipType.RotateNoneFlipY or RotateFlipType.Rotate180FlipX => (RotateMode.None, FlipMode.Vertical), + RotateFlipType.Rotate90FlipY or RotateFlipType.Rotate270FlipX => (RotateMode.Rotate90, FlipMode.Vertical), + _ => throw new ArgumentOutOfRangeException(nameof(rotateFlipType), rotateFlipType, null), + }; + } + + /// + /// Provides enumeration over how a image should be flipped. + /// + public enum FlipMode + { + /// + /// Don't flip the image. + /// + None, + + /// + /// Flip the image horizontally. + /// + Horizontal, + + /// + /// Flip the image vertically. + /// + Vertical + } + + /// + /// Popular image formats which can read and export. + /// + /// + /// + /// + public enum ImageFormat + { + /// The Bitmap image format. + Bmp = 0, + + /// The Gif image format. + Gif = 1, + + /// The Tiff image format. + Tiff = 2, + + /// The Jpeg image format. + Jpeg = 3, + + /// The PNG image format. + Png = 4, + + /// The WBMP image format. Will default to BMP if not + /// supported on the runtime platform. + Wbmp = 5, + + /// The new WebP image format. + Webp = 6, + + /// The Icon image format. + Icon = 7, + + /// The Wmf image format. + Wmf = 8, + + /// The Raw image format. + RawFormat = 9, + + /// The existing raw image format. + Default = -1 + + } + + /// + /// Specifies how much an image is rotated and the axis used to flip + /// the image. This follows the legacy System.Drawing.RotateFlipType + /// notation. + /// + [Obsolete("RotateFlipType is legacy support from System.Drawing. " + + "Please use RotateMode and FlipMode instead.")] + public enum RotateFlipType + { + /// + /// Specifies no clockwise rotation and no flipping. + /// + RotateNoneFlipNone, + /// + /// Specifies a 180-degree clockwise rotation followed by a + /// horizontal and vertical flip. + /// + Rotate180FlipXY, + + /// + /// Specifies a 90-degree clockwise rotation without flipping. + /// + Rotate90FlipNone, + /// + /// Specifies a 270-degree clockwise rotation followed by a + /// horizontal and vertical flip. + /// + Rotate270FlipXY, + + /// + /// Specifies no clockwise rotation followed by a horizontal and + /// vertical flip. + /// + RotateNoneFlipXY, + /// + /// Specifies a 180-degree clockwise rotation without flipping. + /// + Rotate180FlipNone, + + /// + /// Specifies a 90-degree clockwise rotation followed by a + /// horizontal and vertical flip. + /// + Rotate90FlipXY, + /// + /// Specifies a 270-degree clockwise rotation without flipping. + /// + Rotate270FlipNone, + + /// + /// Specifies no clockwise rotation followed by a horizontal flip. + /// + RotateNoneFlipX, + /// + /// Specifies a 180-degree clockwise rotation followed by a + /// vertical flip. + /// + Rotate180FlipY, + + /// + /// Specifies a 90-degree clockwise rotation followed by a + /// horizontal flip. + /// + Rotate90FlipX, + /// + /// Specifies a 270-degree clockwise rotation followed by a + /// vertical flip. + /// + Rotate270FlipY, + + /// + /// Specifies no clockwise rotation followed by a vertical flip. + /// + RotateNoneFlipY, + /// + /// Specifies a 180-degree clockwise rotation followed by a + /// horizontal flip. + /// + Rotate180FlipX, + + /// + /// Specifies a 90-degree clockwise rotation followed by a + /// vertical flip. + /// + Rotate90FlipY, + /// + /// Specifies a 270-degree clockwise rotation followed by a + /// horizontal flip. + /// + Rotate270FlipX + } + + /// + /// Provides enumeration over how the image should be rotated. + /// + public enum RotateMode + { + /// + /// Do not rotate the image. + /// + None, + + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90 = 90, + + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180 = 180, + + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 = 270 + } + } +} \ No newline at end of file diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs index 022b2a0..433df54 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs @@ -15,13 +15,16 @@ using SkiaSharp; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; namespace IronSoftware.Drawing @@ -48,34 +51,101 @@ public partial class AnyBitmap : IDisposable, IAnyImage { private bool _disposed = false; - private Image Image { get; set; } - private byte[] Binary { get; set; } - private IImageFormat Format { get; set; } - private TiffCompression TiffCompression { get; set; } = TiffCompression.Lzw; - private bool PreserveOriginalFormat { get; set; } = true; + /// + /// We use Lazy because in some case we can skip Image.Load (which use a lot of memory). + /// e.g. open jpg file and save it to jpg file without changing anything so we don't need to load the image. + /// + private Lazy> _lazyImage; + + private IReadOnlyList GetInternalImages() + { + return _lazyImage?.Value ?? throw new InvalidOperationException("No image data available"); + } + + private Image GetFirstInternalImage() + { + return (_lazyImage?.Value?[0]) ?? throw new InvalidOperationException("No image data available"); + } + + private void ForceLoadLazyImage() + { + var _ = _lazyImage?.Value; + } + + private readonly object _binaryLock = new object(); + private byte[] _binary; /// - /// Width of the image. + /// This value save the original bytes, we need to update it each time that Image object (inside _lazyImage) is changed /// - public int Width + private byte[] Binary { get { - return Image.Width; + + if (_binary == null) + { + //In case like Binary will be assign once the image is loaded + ForceLoadLazyImage(); //force load but _binary can still be null depended on how _lazyImage was loaded + } + + if (_binary == null || IsDirty) + { + lock (_binaryLock) + { + if (_binary == null || IsDirty) + { + //Which mean we need to update _binary to sync with the image + using var stream = new MemoryStream(); + IImageEncoder enc = GetDefaultImageExportEncoder(); + + GetFirstInternalImage().Save(stream, enc); + _binary = stream.ToArray(); + IsDirty = false; + } + } + } + + return _binary; + } + set + { + _binary = value; } } + private int _isDirty; + /// - /// Height of the image. + /// If IsDirty = true means we need to update Binary. Since Image object (inside _lazyImage) is changed /// - public int Height + private bool IsDirty { - get - { - return Image.Height; - } + // use Interlocked to make sure that it always updated and thread safe. + get => Thread.VolatileRead(ref _isDirty) == 1; + set => Interlocked.Exchange(ref _isDirty, value ? 1 : 0); } + private IImageFormat Format => Image.DetectFormat(Binary); + private TiffCompression TiffCompression { get; set; } = TiffCompression.Lzw; + private bool PreserveOriginalFormat { get; set; } = true; + + //cache since Image.Width (ImageSharp) is slow + private int? _width = null; + + /// + /// Width of the image. + /// + public int Width => _width ??= GetFirstInternalImage().Width; + + //cache since Image.Height (ImageSharp) is slow + private int? _height = null; + + /// + /// Height of the image. + /// + public int Height => _height ??= GetFirstInternalImage().Height; + /// /// Number of raw image bytes stored /// @@ -153,14 +223,8 @@ public AnyBitmap Clone() /// public AnyBitmap Clone(Rectangle rectangle) { - using Image image = Image.Clone(img => img.Crop(rectangle)); - using var memoryStream = new MemoryStream(); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }); - return new AnyBitmap(memoryStream.ToArray()); + var cloned = GetInternalImages().Select(img => img.Clone(x => x.Crop(rectangle))); + return new AnyBitmap(cloned); } /// @@ -273,46 +337,41 @@ public void ExportStream( ImageFormat format = ImageFormat.Default, int lossy = 100) { - if (format is ImageFormat.Default or ImageFormat.RawFormat) - { - var writer = new BinaryWriter(stream); - writer.Write(Binary); - return; - } if (lossy is < 0 or > 100) { lossy = 100; } + //this Check if _lazyImage is not loaded which mean we didn't touch anything and the output format is the same then we should just return the original data + + var isSameFormat = (format is ImageFormat.Default or ImageFormat.RawFormat) || (GetImageFormat() == format); + var isCompressNeeded = (format is ImageFormat.Default or ImageFormat.Webp or ImageFormat.Jpeg) && lossy != 100; + + if (!IsDirty && isSameFormat && !isCompressNeeded) + { + var writer = new BinaryWriter(stream); + writer.Write(Binary); + return; + } + try { - IImageEncoder enc = format switch + IImageEncoder enc = GetDefaultImageExportEncoder(format, lossy); + if (enc is TiffEncoder) { - ImageFormat.Jpeg => new JpegEncoder() - { - Quality = lossy, -#if NET6_0_OR_GREATER - ColorType = JpegEncodingColor.Rgb -#else - ColorType = JpegColorType.Rgb -#endif - }, - ImageFormat.Gif => new GifEncoder(), - ImageFormat.Png => new PngEncoder(), - ImageFormat.Webp => new WebpEncoder() { Quality = lossy }, - ImageFormat.Tiff => new TiffEncoder() - { - Compression = TiffCompression - }, - _ => new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }, - }; + InternalSaveAsMultiPageTiff(_lazyImage?.Value, stream); + } + else if (enc is GifEncoder) + { + InternalSaveAsMultiPageGif(_lazyImage?.Value, stream); + + } + else + { + GetFirstInternalImage().Save(stream, enc); + } - Image.Save(stream, enc); } catch (DllNotFoundException e) { @@ -500,7 +559,7 @@ public static AnyBitmap FromBytes(byte[] bytes, bool preserveOriginalFormat) /// /// Create a new Bitmap from a (bytes). /// - /// A of image data in any common format. + /// A of image data in any common format. /// Default is true. Set to false to load as Rgba32. /// /// @@ -716,9 +775,69 @@ public AnyBitmap(Uri uri, bool preserveOriginalFormat) /// Background color of new AnyBitmap public AnyBitmap(int width, int height, Color backgroundColor = null) { - CreateNewImageInstance(width, height, backgroundColor); + _lazyImage = new Lazy>(() => + { + var image = new Image(width, height); + if (backgroundColor != null) + { + image.Mutate(context => context.Fill(backgroundColor)); + } + return [image]; + }); + ForceLoadLazyImage(); + } + + /// + /// Construct an AnyBitmap object from a buffer of RGB pixel data. + /// + /// An array of bytes representing the RGB pixel data. This should contain 3 bytes (one each for red, green, and blue) for each pixel in the image. + /// The width of the image, in pixels. + /// The height of the image, in pixels. + /// An AnyBitmap object that represents the image defined by the provided pixel data, width, and height. + internal AnyBitmap(byte[] buffer, int width, int height) + { + _lazyImage = new Lazy>(() => + { + var image = Image.LoadPixelData(buffer, width, height); + return [image]; + }); + } + + /// + /// Note: This only use for Casting It won't create new object Image + /// + /// + internal AnyBitmap(Image image) : this([image]) + { + } + + /// + /// Note: This only use for Casting It won't create new object Image + /// + /// + internal AnyBitmap(IEnumerable images) + { + _lazyImage = new Lazy>(() => + { + return [.. images]; + }); + } + + /// + /// Fastest AnyBitmap ctor + /// + /// + /// + internal AnyBitmap(byte[] bytes, IEnumerable images) + { + Binary = bytes; + _lazyImage = new Lazy>(() => + { + return [.. images]; + }); } + /// /// Create a new Bitmap from a file. /// @@ -855,29 +974,21 @@ public static AnyBitmap FromUri(Uri uri, bool preserveOriginalFormat) /// An AnyBitmap object that represents the image defined by the provided pixel data, width, and height. public static AnyBitmap LoadAnyBitmapFromRGBBuffer(byte[] buffer, int width, int height) { - using var memoryStream = new MemoryStream(); - using var image = Image.LoadPixelData(buffer, width, height); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel24 - }); - return new AnyBitmap(memoryStream.ToArray()); + return new AnyBitmap(buffer, width, height); } + //cache + private int? _bitsPerPixel = null; /// /// Gets colors depth, in number of bits per pixel. ///
Further Documentation:
/// /// Code Example
///
- public int BitsPerPixel - { - get - { - return Image.PixelType.BitsPerPixel; - } - } + public int BitsPerPixel => _bitsPerPixel ??= GetFirstInternalImage().PixelType.BitsPerPixel; + //cache + private int? _frameCount = null; /// /// Returns the number of frames in our loaded Image. Each “frame” is /// a page of an image such as Tiff or Gif. All other image formats @@ -891,12 +1002,17 @@ public int FrameCount { get { - return Image.Frames.Count; + if (!_frameCount.HasValue) + { + var images = GetInternalImages(); + _frameCount = images.Count == 1 ? images[0].Frames.Count : images.Count; + } + return _frameCount.Value; } } /// - /// Returns all of the cloned frames in our loaded Image. Each "frame" + /// Returns all of the frames in our loaded Image. Each "frame" /// is a page of an image such as Tiff or Gif. All other image formats /// return an IEnumerable of length 1. ///
Further Documentation:
@@ -909,20 +1025,14 @@ public IEnumerable GetAllFrames { get { - if (FrameCount > 1) + var images = GetInternalImages(); + if (images.Count == 1) { - List images = new(); - - for (int currFrameIndex = 0; currFrameIndex < FrameCount; currFrameIndex++) - { - images.Add(Image.Frames.CloneFrame(currFrameIndex)); - } - - return images; + return ImageFrameCollectionToImages(images[0].Frames).Select(x => (AnyBitmap)x); } else { - return new List() { Clone() }; + return images.Select(x => (AnyBitmap)x); } } } @@ -939,10 +1049,8 @@ public IEnumerable GetAllFrames /// public static AnyBitmap CreateMultiFrameTiff(IEnumerable imagePaths) { - using MemoryStream stream = - CreateMultiFrameImage(CreateAnyBitmaps(imagePaths)) - ?? throw new NotSupportedException("Image could not be loaded. File format is not supported."); - _ = stream.Seek(0, SeekOrigin.Begin); + using var stream = new MemoryStream(); + InternalSaveAsMultiPageTiff(imagePaths.Select(Image.Load), stream); return FromStream(stream); } @@ -959,10 +1067,8 @@ public static AnyBitmap CreateMultiFrameTiff(IEnumerable imagePaths) /// public static AnyBitmap CreateMultiFrameTiff(IEnumerable images) { - using MemoryStream stream = - CreateMultiFrameImage(images) - ?? throw new NotSupportedException("Image could not be loaded. File format is not supported."); - _ = stream.Seek(0, SeekOrigin.Begin); + using var stream = new MemoryStream(); + InternalSaveAsMultiPageTiff(images.Select(x => (Image)x), stream); return FromStream(stream); } @@ -979,10 +1085,8 @@ public static AnyBitmap CreateMultiFrameTiff(IEnumerable images) /// public static AnyBitmap CreateMultiFrameGif(IEnumerable imagePaths) { - using MemoryStream stream = - CreateMultiFrameImage(CreateAnyBitmaps(imagePaths), ImageFormat.Gif) - ?? throw new NotSupportedException("Image could not be loaded. File format is not supported."); - _ = stream.Seek(0, SeekOrigin.Begin); + using var stream = new MemoryStream(); + InternalSaveAsMultiPageGif(imagePaths.Select(Image.Load), stream); return FromStream(stream); } @@ -999,10 +1103,8 @@ public static AnyBitmap CreateMultiFrameGif(IEnumerable imagePaths) /// public static AnyBitmap CreateMultiFrameGif(IEnumerable images) { - using MemoryStream stream = - CreateMultiFrameImage(images, ImageFormat.Gif) - ?? throw new NotSupportedException("Image could not be loaded. File format is not supported."); - _ = stream.Seek(0, SeekOrigin.Begin); + using var stream = new MemoryStream(); + InternalSaveAsMultiPageGif(images.Select(x => (Image)x), stream); return FromStream(stream); } @@ -1013,37 +1115,81 @@ public static AnyBitmap CreateMultiFrameGif(IEnumerable images) /// Thrown when the image's bit depth is not 32 bpp. public byte[] ExtractAlphaData() { - if (BitsPerPixel == 32) + + var alpha = new byte[Width * Height]; + + switch (GetFirstInternalImage()) { - var alpha = new byte[Image.Width * Image.Height]; - int alphaIndex = 0; - using var rgbaImage = Image is Image image - ? image - : Image.CloneAs(); - rgbaImage.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => { - // Get the row as a span of Rgba32. - Span pixelRow = accessor.GetRowSpan(y); - // Interpret the row as a span of bytes. - Span rowBytes = MemoryMarshal.AsBytes(pixelRow); - - // Each pixel is 4 bytes: R, G, B, A. - // The alpha channel is the fourth byte (index 3, 7, 11, ...). - for (int i = 3; i < rowBytes.Length; i += 4) + for (int y = 0; y < accessor.Height; y++) { - alpha[alphaIndex++] = rowBytes[i]; + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) + { + alpha[y * accessor.Width + x] = pixelRow[x].A; + } } - } - }); - - return alpha.ToArray(); - } - else - { - throw new NotSupportedException($"Extracting alpha data is not supported for {BitsPerPixel} bpp images."); + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) + { + alpha[y * accessor.Width + x] = pixelRow[x].A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) + { + alpha[y * accessor.Width + x] = pixelRow[x].A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) + { + alpha[y * accessor.Width + x] = pixelRow[x].A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) + { + alpha[y * accessor.Width + x] = pixelRow[x].ToRgba32().A; + } + } + }); + break; + default: + throw new NotSupportedException($"Extracting alpha data is not supported for {BitsPerPixel} bpp images."); } + + return alpha.ToArray(); } /// @@ -1108,17 +1254,11 @@ public static AnyBitmap RotateFlip( _ => throw new NotImplementedException() }; - using var memoryStream = new MemoryStream(); - using var image = Image.Load(bitmap.ExportBytes()); + Image image = Image.Load(bitmap.Binary); image.Mutate(x => x.RotateFlip(rotateModeImgSharp, flipModeImgSharp)); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }); - return new AnyBitmap(memoryStream.ToArray()); + return new AnyBitmap(image); } /// @@ -1148,18 +1288,14 @@ public static AnyBitmap Redact( Rectangle Rectangle, Color color) { - using var memoryStream = new MemoryStream(); - using var image = Image.Load(bitmap.ExportBytes()); + + //this casting will crate new object + Image image = Image.Load(bitmap.Binary); Rectangle rectangle = Rectangle; var brush = new SolidBrush(color); image.Mutate(ctx => ctx.Fill(brush, rectangle)); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }); - - return new AnyBitmap(memoryStream.ToArray()); + + return new AnyBitmap(image); } /// @@ -1231,7 +1367,7 @@ public double? HorizontalResolution { get { - return Image?.Metadata.HorizontalResolution ?? null; + return GetFirstInternalImage().Metadata.HorizontalResolution; } } @@ -1243,7 +1379,7 @@ public double? VerticalResolution { get { - return Image?.Metadata.VerticalResolution ?? null; + return GetFirstInternalImage().Metadata.VerticalResolution; } } @@ -1274,8 +1410,8 @@ public Color GetPixel(int x, int y) } /// - /// Sets the of the specified pixel in this - /// + /// Sets the of the specified pixel in this + /// Performs an operation that modifies the current object. (mutable) /// Set in Rgb24 color format. /// /// The x-coordinate of the pixel to retrieve. @@ -1295,7 +1431,6 @@ public void SetPixel(int x, int y, Color color) throw new ArgumentOutOfRangeException(nameof(y), "y is less than 0, or greater than or equal to Height."); } - SetPixelColor(x, y, color); } @@ -1309,89 +1444,459 @@ public void SetPixel(int x, int y, Color color) /// public byte[] GetRGBBuffer() { - using Image image = Image.CloneAs(); - + var image = GetFirstInternalImage(); int width = image.Width; int height = image.Height; - byte[] rgbBuffer = new byte[width * height * 3]; // 3 bytes per pixel (RGB) - - image.ProcessPixelRows(accessor => + switch (image) { - for (int y = 0; y < accessor.Height; y++) - { - Span pixelRow = accessor.GetRowSpan(y); - - for (int x = 0; x < accessor.Width; x++) + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => { - ref Rgb24 pixel = ref pixelRow[x]; - - int bufferIndex = (y * width + x) * 3; - rgbBuffer[bufferIndex] = pixel.R; - rgbBuffer[bufferIndex + 1] = pixel.G; - rgbBuffer[bufferIndex + 2] = pixel.B; - } - } - }); - + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgba32 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgb24 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Abgr32 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Argb32 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Bgr24 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Bgra32 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + //required casting in 16bit color + Color pixel = (Color)pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + //required casting in 16bit color + Color pixel = (Color)pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + break; + default: + var clonedImage = image.CloneAs(); + clonedImage.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgb24 pixel = pixelRow[x]; + int index = (y * width + x) * 3; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + } + } + }); + clonedImage.Dispose(); + break; + } return rgbBuffer; } - #region Implicit Casting - /// - /// Implicitly casts SixLabors.ImageSharp.Image objects to - /// . - /// When your .NET Class methods use as - /// parameters or return types, you now automatically support ImageSharp - /// as well. - /// When casting to and from AnyBitmap, - /// please remember to dispose your original SixLabors.ImageSharp.Image object - /// to avoid unnecessary memory allocation. + /// Retrieves the RGBA buffer from the image at the specified path. /// - /// SixLabors.ImageSharp.Image will automatically - /// be casted to . - public static implicit operator AnyBitmap(Image image) + /// An array of bytes representing the RGBA buffer of the image. + /// + /// Each pixel is represented by four bytes in the order: red, green, blue, alpha. + /// The pixels are read from the image row by row, from top to bottom and left to right within each row. + /// + public byte[] GetRGBABuffer() { - try - { - using var memoryStream = new MemoryStream(); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel24 - }); - return new AnyBitmap(memoryStream.ToArray()); - - } - catch (DllNotFoundException e) - { - throw new DllNotFoundException( - "Please install SixLabors.ImageSharp from NuGet.", e); - } - catch (Exception e) + var image = GetFirstInternalImage(); + int width = image.Width; + int height = image.Height; + byte[] rgbBuffer = new byte[width * height * 4]; // 3 bytes per pixel (RGB) + switch (image) { - throw new NotSupportedException( - "Error while casting AnyBitmap from SixLabors.ImageSharp.Image", e); - } + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgba32 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgb24 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = byte.MaxValue; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Abgr32 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Argb32 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Bgr24 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = byte.MaxValue; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Bgra32 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + //required casting in 16bit color + Color pixel = (Color)pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + case Image imageAsFormat: + imageAsFormat.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + //required casting in 16bit color + Color pixel = (Color)pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + break; + default: + using (var clonedImage = image.CloneAs()) + { + clonedImage.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + for (int x = 0; x < width; x++) + { + Rgba32 pixel = pixelRow[x]; + int index = (y * width + x) * 4; + + rgbBuffer[index] = pixel.R; + rgbBuffer[index + 1] = pixel.G; + rgbBuffer[index + 2] = pixel.B; + rgbBuffer[index + 3] = pixel.A; + } + } + }); + } + break; + } + return rgbBuffer; } + #region Implicit Casting + /// - /// Implicitly casts to SixLabors.ImageSharp.Image objects from + /// Implicitly casts SixLabors.ImageSharp.Image objects to /// . - /// When your .NET Class methods use - /// as parameters or return types, you now automatically support - /// ImageSharp as well. + /// When your .NET Class methods use as + /// parameters or return types, you now automatically support ImageSharp + /// as well. /// When casting to and from AnyBitmap, - /// please remember to dispose your original IronSoftware.Drawing.AnyBitmap object + /// please remember to dispose your original SixLabors.ImageSharp.Image object /// to avoid unnecessary memory allocation. /// - /// is implicitly cast to - /// a SixLabors.ImageSharp.Image. - public static implicit operator Image(AnyBitmap bitmap) + /// SixLabors.ImageSharp.Image will automatically + /// be casted to . + public static implicit operator AnyBitmap(Image image) { try { - return Image.Load(bitmap.Binary); + return new AnyBitmap(image); + + } + catch (DllNotFoundException e) + { + throw new DllNotFoundException( + "Please install SixLabors.ImageSharp from NuGet.", e); + } + catch (Exception e) + { + throw new NotSupportedException( + "Error while casting AnyBitmap from SixLabors.ImageSharp.Image", e); + } + } + + /// + /// Since we store ImageSharp object internal AnyBitmap (lazy) so this casting will return the same ImageSharp object if it loaded. + /// But if it is gif/tiff we need to make resize all frame to have the same size before we load to ImageSharp object; + /// + private static Image CastToImageSharp(AnyBitmap bitmap) + { + try + { + if (!bitmap.IsImageLoaded()) + { + var format = bitmap.Format; + if (format is not TiffFormat && format is not GifFormat) + { + return Image.Load(bitmap.Binary); + } + } + + //if it is loaded or gif/tiff + var images = bitmap.GetInternalImages(); + if (images.Count == 1) + { + //not gif/tiff + return images[0]; + } + else + { + //for gif/tiff we need to resize all frame + //Tiff can have different frame size but ImageSharp does not support + var resultImage = images[0].Clone((_) => { }); + + foreach (var frame in images.Skip(1)) + { + var newFrame = frame.Clone(x => + { + x.Resize(new ResizeOptions + { + Size = new Size(resultImage.Width, resultImage.Height), + Mode = ResizeMode.BoxPad, // Pad to fit the target dimensions + PadColor = Color.Transparent, // Use transparent padding + Position = AnchorPositionMode.Center // Center the image within the frame + }); + }); + + resultImage.Frames.AddFrame(newFrame.Frames.RootFrame); + } + + return resultImage; + } } catch (DllNotFoundException e) { @@ -1405,6 +1910,88 @@ public static implicit operator Image(AnyBitmap bitmap) } } + /// + /// Since we store ImageSharp object internal AnyBitmap (lazy) so this casting will return the same ImageSharp object if it loaded. + /// But if it is gif/tiff we need to make resize all frame to have the same size before we load to ImageSharp object; + /// + private static Image CastToImageSharp(AnyBitmap bitmap) where T :unmanaged, SixLabors.ImageSharp.PixelFormats.IPixel + { + try + { + if (!bitmap.IsImageLoaded()) + { + var format = bitmap.Format; + if (format is not TiffFormat && format is not GifFormat) + { + return Image.Load(bitmap.Binary); + } + + } + + var images = bitmap.GetInternalImages(); + if (images.Count == 1) + { + if (images[0] is Image correctType) + { + return correctType; + } + else + { + return images[0].CloneAs(); + } + } + else + { + var resultImage = images[0].CloneAs(); + + //for gif/tiff we need to resize all frame + //Tiff can have different frame size but ImageSharp does not support + foreach (var frame in images.Skip(1)) + { + var newFrame = frame.CloneAs(); + + newFrame.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(resultImage.Width, resultImage.Height), + Mode = ResizeMode.BoxPad, // Pad to fit the target dimensions + PadColor = Color.Transparent, // Use transparent padding + Position = AnchorPositionMode.Center // Center the image within the frame + })); + resultImage.Frames.AddFrame(newFrame.Frames.RootFrame); + } + + return resultImage; + } + } + catch (DllNotFoundException e) + { + throw new DllNotFoundException( + "Please install SixLabors.ImageSharp from NuGet.", e); + } + catch (Exception e) + { + throw new NotSupportedException( + "Error while casting AnyBitmap to SixLabors.ImageSharp.Image", e); + } + } + + /// + /// Implicitly casts to SixLabors.ImageSharp.Image objects from + /// . + /// When your .NET Class methods use + /// as parameters or return types, you now automatically support + /// ImageSharp as well. + /// When casting to and from AnyBitmap, + /// please remember to dispose your original IronSoftware.Drawing.AnyBitmap object + /// to avoid unnecessary memory allocation. + /// + /// is implicitly cast to + /// a SixLabors.ImageSharp.Image. + public static implicit operator Image(AnyBitmap bitmap) + { + return CastToImageSharp(bitmap); + } + /// /// Implicitly casts SixLabors.ImageSharp.Image objects to /// . @@ -1421,13 +2008,7 @@ public static implicit operator AnyBitmap(Image image) { try { - using var memoryStream = new MemoryStream(); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }); - return new AnyBitmap(memoryStream.ToArray()); + return new AnyBitmap(image); } catch (DllNotFoundException e) { @@ -1457,7 +2038,7 @@ public static implicit operator Image(AnyBitmap bitmap) { try { - return Image.Load(bitmap.Binary); + return CastToImageSharp(bitmap); } catch (DllNotFoundException e) { @@ -1487,13 +2068,7 @@ public static implicit operator AnyBitmap(Image image) { try { - using var memoryStream = new MemoryStream(); - image.Save(memoryStream, new BmpEncoder() - { - BitsPerPixel = BmpBitsPerPixel.Pixel32, - SupportTransparency = true - }); - return new AnyBitmap(memoryStream.ToArray()); + return new AnyBitmap(image); } catch (DllNotFoundException e) { @@ -1523,7 +2098,7 @@ public static implicit operator Image(AnyBitmap bitmap) { try { - return Image.Load(bitmap.Binary); + return CastToImageSharp(bitmap); } catch (DllNotFoundException e) { @@ -1825,7 +2400,7 @@ public static implicit operator System.Drawing.Bitmap(AnyBitmap bitmap) { try { - return (System.Drawing.Bitmap)System.Drawing.Image.FromStream(new MemoryStream(bitmap.Binary)); + return (System.Drawing.Bitmap)System.Drawing.Image.FromStream(bitmap.GetStream()); } catch (DllNotFoundException e) { @@ -1922,7 +2497,7 @@ public static implicit operator System.Drawing.Image(AnyBitmap bitmap) { try { - return System.Drawing.Image.FromStream(new MemoryStream(bitmap.Binary)); + return System.Drawing.Image.FromStream(bitmap.GetStream()); } catch (DllNotFoundException e) { @@ -1943,214 +2518,7 @@ public static implicit operator System.Drawing.Image(AnyBitmap bitmap) throw new Exception(e.Message, e); } } - #endregion - #region Enum Classes - - /// - /// Popular image formats which can read and export. - /// - /// - /// - /// - public enum ImageFormat - { - /// The Bitmap image format. - Bmp = 0, - - /// The Gif image format. - Gif = 1, - - /// The Tiff image format. - Tiff = 2, - - /// The Jpeg image format. - Jpeg = 3, - - /// The PNG image format. - Png = 4, - - /// The WBMP image format. Will default to BMP if not - /// supported on the runtime platform. - Wbmp = 5, - - /// The new WebP image format. - Webp = 6, - - /// The Icon image format. - Icon = 7, - - /// The Wmf image format. - Wmf = 8, - - /// The Raw image format. - RawFormat = 9, - - /// The existing raw image format. - Default = -1 - - } -# pragma warning disable CS0618 - /// - /// Converts the legacy to and - /// - [Obsolete("RotateFlipType is legacy support from System.Drawing. " + - "Please use RotateMode and FlipMode instead.")] - internal static (RotateMode, FlipMode) ParseRotateFlipType(RotateFlipType rotateFlipType) - { - return rotateFlipType switch - { - RotateFlipType.RotateNoneFlipNone or RotateFlipType.Rotate180FlipXY => (RotateMode.None, FlipMode.None), - RotateFlipType.Rotate90FlipNone or RotateFlipType.Rotate270FlipXY => (RotateMode.Rotate90, FlipMode.None), - RotateFlipType.RotateNoneFlipXY or RotateFlipType.Rotate180FlipNone => (RotateMode.Rotate180, FlipMode.None), - RotateFlipType.Rotate90FlipXY or RotateFlipType.Rotate270FlipNone => (RotateMode.Rotate270, FlipMode.None), - RotateFlipType.RotateNoneFlipX or RotateFlipType.Rotate180FlipY => (RotateMode.None, FlipMode.Horizontal), - RotateFlipType.Rotate90FlipX or RotateFlipType.Rotate270FlipY => (RotateMode.Rotate90, FlipMode.Horizontal), - RotateFlipType.RotateNoneFlipY or RotateFlipType.Rotate180FlipX => (RotateMode.None, FlipMode.Vertical), - RotateFlipType.Rotate90FlipY or RotateFlipType.Rotate270FlipX => (RotateMode.Rotate90, FlipMode.Vertical), - _ => throw new ArgumentOutOfRangeException(nameof(rotateFlipType), rotateFlipType, null), - }; - } -# pragma warning restore CS0618 - - /// - /// Provides enumeration over how the image should be rotated. - /// - public enum RotateMode - { - /// - /// Do not rotate the image. - /// - None, - - /// - /// Rotate the image by 90 degrees clockwise. - /// - Rotate90 = 90, - - /// - /// Rotate the image by 180 degrees clockwise. - /// - Rotate180 = 180, - - /// - /// Rotate the image by 270 degrees clockwise. - /// - Rotate270 = 270 - } - - /// - /// Provides enumeration over how a image should be flipped. - /// - public enum FlipMode - { - /// - /// Don't flip the image. - /// - None, - - /// - /// Flip the image horizontally. - /// - Horizontal, - - /// - /// Flip the image vertically. - /// - Vertical - } - - /// - /// Specifies how much an image is rotated and the axis used to flip - /// the image. This follows the legacy System.Drawing.RotateFlipType - /// notation. - /// - [Obsolete("RotateFlipType is legacy support from System.Drawing. " + - "Please use RotateMode and FlipMode instead.")] - public enum RotateFlipType - { - /// - /// Specifies no clockwise rotation and no flipping. - /// - RotateNoneFlipNone, - /// - /// Specifies a 180-degree clockwise rotation followed by a - /// horizontal and vertical flip. - /// - Rotate180FlipXY, - - /// - /// Specifies a 90-degree clockwise rotation without flipping. - /// - Rotate90FlipNone, - /// - /// Specifies a 270-degree clockwise rotation followed by a - /// horizontal and vertical flip. - /// - Rotate270FlipXY, - - /// - /// Specifies no clockwise rotation followed by a horizontal and - /// vertical flip. - /// - RotateNoneFlipXY, - /// - /// Specifies a 180-degree clockwise rotation without flipping. - /// - Rotate180FlipNone, - - /// - /// Specifies a 90-degree clockwise rotation followed by a - /// horizontal and vertical flip. - /// - Rotate90FlipXY, - /// - /// Specifies a 270-degree clockwise rotation without flipping. - /// - Rotate270FlipNone, - - /// - /// Specifies no clockwise rotation followed by a horizontal flip. - /// - RotateNoneFlipX, - /// - /// Specifies a 180-degree clockwise rotation followed by a - /// vertical flip. - /// - Rotate180FlipY, - - /// - /// Specifies a 90-degree clockwise rotation followed by a - /// horizontal flip. - /// - Rotate90FlipX, - /// - /// Specifies a 270-degree clockwise rotation followed by a - /// vertical flip. - /// - Rotate270FlipY, - - /// - /// Specifies no clockwise rotation followed by a vertical flip. - /// - RotateNoneFlipY, - /// - /// Specifies a 180-degree clockwise rotation followed by a - /// horizontal flip. - /// - Rotate180FlipX, - - /// - /// Specifies a 90-degree clockwise rotation followed by a - /// vertical flip. - /// - Rotate90FlipY, - /// - /// Specifies a 270-degree clockwise rotation followed by a - /// horizontal flip. - /// - Rotate270FlipX - } #endregion /// @@ -2181,9 +2549,14 @@ protected virtual void Dispose(bool disposing) { return; } - - Image?.Dispose(); - Image = null; + if (IsImageLoaded()) + { + foreach (var x in GetInternalImages() ?? []) + { + x.Dispose(); + } + } + _lazyImage = null; Binary = null; _disposed = true; } @@ -2191,93 +2564,76 @@ protected virtual void Dispose(bool disposing) #region Private Method - private void CreateNewImageInstance(int width, int height, Color backgroundColor) + private void LoadImage(Stream stream, bool preserveOriginalFormat) { - Image = new Image(width, height); - if (backgroundColor != null) + // Optimization 1: If the stream is already a MemoryStream, we can get its + // underlying array directly, avoiding a full copy cycle. + if (stream is MemoryStream memoryStream) { - Image.Mutate(context => context.Fill(backgroundColor)); + LoadImage(memoryStream.ToArray(), preserveOriginalFormat); + return; } - using var stream = new MemoryStream(); - Image.SaveAsBmp(stream); - Binary = stream.ToArray(); - } - - private void LoadImage(ReadOnlySpan bytes, bool preserveOriginalFormat) - { - Format = Image.DetectFormat(bytes); - try + + // Optimization 2: If the stream can report its length (like a FileStream), + // we can create a MemoryStream with the exact capacity needed. This avoids + // multiple buffer re-allocations as the MemoryStream grows. + if (stream.CanSeek) { - if (Format is TiffFormat) - OpenTiffToImageSharp(bytes); - else + // Ensure we read from the beginning of the stream. + stream.Position = 0; + using (var ms = new MemoryStream((int)stream.Length)) { - Binary = bytes.ToArray(); - - if (preserveOriginalFormat) - Image = Image.Load(bytes); - else - { - PreserveOriginalFormat = preserveOriginalFormat; - Image = Image.Load(bytes); + stream.CopyTo(ms, 16 * 1024); + LoadImage(ms.ToArray(), preserveOriginalFormat); + return; + } + } - // .png image pre-processing - if (Format.Name == "PNG") - Image.Mutate(img => img.BackgroundColor(SixLabors.ImageSharp.Color.White)); - } + // Fallback for non-seekable streams (e.g., a network stream). + // This is the most memory-intensive path, but necessary for this stream type. + // We use CopyTo for a cleaner implementation of the original logic. + using (var ms = new MemoryStream()) + { + stream.CopyTo(ms, 16 * 1024); + LoadImage(ms.ToArray(), preserveOriginalFormat); + } - // 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; + } - // Check if image metadata is accurate already - switch (resolutionUnit) - { - case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerMeter: - // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 meter = 37.3701 inches - this.Image.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; - this.Image.Metadata.HorizontalResolution = Math.Ceiling(horizontal / 39.3701); - this.Image.Metadata.VerticalResolution = Math.Ceiling(vertical / 39.3701); - break; - case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerCentimeter: - // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 inch = 2.54 centimeters - this.Image.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; - this.Image.Metadata.HorizontalResolution = Math.Ceiling(horizontal * 2.54); - this.Image.Metadata.VerticalResolution = Math.Ceiling(vertical * 2.54); - break; - default: - // No changes required due to teh metadata are accurate already - break; - } - } - } - catch (DllNotFoundException e) + /// + /// Master LoadImage method + /// + /// + /// + private void LoadImage(ReadOnlySpan span, bool preserveOriginalFormat) + { + Binary = span.ToArray(); + if (Format is TiffFormat) { - throw new DllNotFoundException( - "Please install SixLabors.ImageSharp from NuGet.", e); + if(GetTiffFrameCountFast() > 1) + { + _lazyImage = OpenTiffToImageSharp(); + } + else + { + // ImageSharp can load some single frame tiff, if failed we try again with LibTiff + _lazyImage = OpenImageToImageSharp(preserveOriginalFormat, tryWithLibTiff : true); + } + } - catch (Exception e) + else { - throw new NotSupportedException( - "Image could not be loaded. File format is not supported.", e); + _lazyImage = OpenImageToImageSharp(preserveOriginalFormat); } } - private void LoadImage(Stream stream, bool preserveOriginalFormat) + private IEnumerable ImageFrameCollectionToImages(ImageFrameCollection imageFrames) { - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new(); - int read; - while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + for (int i = 0; i < imageFrames.Count; i++) { - ms.Write(buffer, 0, read); + yield return imageFrames.CloneFrame(i); } - - LoadImage(ms.ToArray(), preserveOriginalFormat); } private static AnyBitmap LoadSVGImage(string file, bool preserveOriginalFormat) @@ -2448,112 +2804,172 @@ public override void WarningHandlerExt(Tiff tif, object clientData, string metho } } - private void OpenTiffToImageSharp(ReadOnlySpan bytes) + private int GetTiffFrameCountFast() { try { - int imageWidth = 0; - int imageHeight = 0; - double imageXResolution = 0; - double imageYResolution = 0; - List images = new(); - - // create a memory stream out of them - using MemoryStream tiffStream = new(bytes.ToArray()); + using var tiffStream = new MemoryStream(Binary); - // Disable warning messages + // Disable error messages for fast check Tiff.SetErrorHandler(new DisableErrorHandler()); - // open a TIFF stored in the stream - using (Tiff tiff = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream())) + using var tiff = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()); + if (tiff == null) return 1; // Default to single frame if can't read + + return tiff.NumberOfDirectories(); + } + catch + { + return 1; // Default to single frame on any error + } + } + + private Lazy> OpenTiffToImageSharp() + { + return new Lazy>(() => + { + try + { + return InternalLoadTiff(); + } + catch (DllNotFoundException e) + { + throw new DllNotFoundException("Please install BitMiracle.LibTiff.NET from NuGet.", e); + } + catch (Exception e) { - SetTiffCompression(tiff); + throw new NotSupportedException("Error while reading TIFF image format.", e); + } + }); + } - short num = tiff.NumberOfDirectories(); - for (short i = 0; i < num; i++) - { - _ = tiff.SetDirectory(i); + private IReadOnlyList InternalLoadTiff() + { + int imageWidth = 0; + int imageHeight = 0; + double imageXResolution = 0; + double imageYResolution = 0; + //IEnumerable images = new(); - if (IsThumbnail(tiff)) - { - continue; - } + // create a memory stream out of them + using MemoryStream tiffStream = new(Binary); - var (width, height, horizontalResolution, verticalResolution) = SetWidthHeight(tiff, i, ref imageWidth, ref imageHeight, ref imageXResolution, ref imageYResolution); + // Disable warning messages + Tiff.SetErrorHandler(new DisableErrorHandler()); + List images = new(); + // open a TIFF stored in the stream + using (Tiff tiff = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream())) + { + SetTiffCompression(tiff); - // Read the image into the memory buffer - int[] raster = new int[height * width]; - if (!tiff.ReadRGBAImage(width, height, raster)) - { - throw new NotSupportedException("Could not read image"); - } + short num = tiff.NumberOfDirectories(); + for (short i = 0; i < num; i++) + { + _ = tiff.SetDirectory(i); - using Image bmp = new(width, height); + if (IsThumbnail(tiff)) + { + continue; + } - var bits = PrepareByteArray(bmp, raster, width, height); + var (width, height, horizontalResolution, verticalResolution) = SetWidthHeight(tiff, i, ref imageWidth, ref imageHeight, ref imageXResolution, ref imageYResolution); - images.Add(Image.LoadPixelData(bits, bmp.Width, bmp.Height)); - - // Update the metadata for image resolutions - images[0].Metadata.HorizontalResolution = horizontalResolution; - images[0].Metadata.VerticalResolution = verticalResolution; + // Read the image into the memory buffer + int[] raster = new int[height * width]; + if (!tiff.ReadRGBAImage(width, height, raster)) + { + throw new NotSupportedException("Could not read image"); } - } + var bits = PrepareByteArray(raster, width, height, 32); + + var image = Image.LoadPixelData(bits, width, height); + image.Metadata.HorizontalResolution = horizontalResolution; + image.Metadata.VerticalResolution = verticalResolution; + images.Add(image); - // find max - FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight); + //Note1: it might be some case that the bytes of current Image is smaller/bigger than the original tiff + //Note2: 'yield return' make it super slow + } - // mute first image - images[0].Mutate(img => img.Resize(new ResizeOptions - { - Size = new Size(maxWidth, maxHeight), - Mode = ResizeMode.BoxPad, - PadColor = SixLabors.ImageSharp.Color.Transparent - })); + } + return images; + } - // iterate through images past the first - for (int i = 1; i < images.Count; i++) + private Lazy> OpenImageToImageSharp(bool preserveOriginalFormat, bool tryWithLibTiff = false) + { + return new Lazy>(() => + { + try { - // mute image - images[i].Mutate(img => img.Resize(new ResizeOptions + Image img; + if (preserveOriginalFormat) { - Size = new Size(maxWidth, maxHeight), - Mode = ResizeMode.BoxPad, - PadColor = SixLabors.ImageSharp.Color.Transparent - })); + img = Image.Load(Binary); + } + else + { + PreserveOriginalFormat = preserveOriginalFormat; + img = Image.Load(Binary); + if (Format.Name == "PNG") + img.Mutate(img => img.BackgroundColor(SixLabors.ImageSharp.Color.White)); + } - // add frames to first image - _ = images[0].Frames.AddFrame(images[i].Frames.RootFrame); + CorrectImageSharp(img); - // dispose images past the first - images[i].Dispose(); - } + return [img]; + } + catch (DllNotFoundException e) + { + throw new DllNotFoundException( + "Please install SixLabors.ImageSharp from NuGet.", e); + } + catch (Exception e) + { + return tryWithLibTiff + ? InternalLoadTiff() + : throw new NotSupportedException( + "Image could not be loaded. File format is not supported.", e); + } + }); + } - // get raw binary - using var memoryStream = new MemoryStream(); - images[0].Save(memoryStream, new TiffEncoder()); - memoryStream.Seek(0, SeekOrigin.Begin); + private void CorrectImageSharp(Image img) + { - // store result - Binary = memoryStream.ToArray(); - Image?.Dispose(); - Image = images[0]; - } - catch (DllNotFoundException e) - { - throw new DllNotFoundException("Please install BitMiracle.LibTiff.NET from NuGet.", e); - } - catch (Exception e) + // Fix if the input image is auto-rotated; this issue is acknowledged by SixLabors.ImageSharp community + // ref: https://github.com/SixLabors/ImageSharp/discussions/2685 + img.Mutate(x => x.AutoOrient()); + + var resolutionUnit = img.Metadata.ResolutionUnits; + var horizontal = img.Metadata.HorizontalResolution; + var vertical = img.Metadata.VerticalResolution; + + // Check if image metadata is accurate already + switch (resolutionUnit) { - throw new NotSupportedException("Error while reading TIFF image format.", e); + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerMeter: + // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 meter = 37.3701 inches + img.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; + img.Metadata.HorizontalResolution = Math.Ceiling(horizontal / 39.3701); + img.Metadata.VerticalResolution = Math.Ceiling(vertical / 39.3701); + break; + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerCentimeter: + // Convert metadata of the resolution unit to pixel per inch to match the conversion below of 1 inch = 2.54 centimeters + img.Metadata.ResolutionUnits = SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch; + img.Metadata.HorizontalResolution = Math.Ceiling(horizontal * 2.54); + img.Metadata.VerticalResolution = Math.Ceiling(vertical * 2.54); + break; + default: + // No changes required due to teh metadata are accurate already + break; } } private void SetTiffCompression(Tiff tiff) { - Compression tiffCompression = tiff.GetField(TiffTag.COMPRESSION) != null && tiff.GetField(TiffTag.COMPRESSION).Length > 0 + Compression tiffCompression = tiff.GetField(TiffTag.COMPRESSION) != null && tiff.GetField(TiffTag.COMPRESSION).Length > 0 ? (Compression)tiff.GetField(TiffTag.COMPRESSION)[0].ToInt() : Compression.NONE; @@ -2584,13 +3000,14 @@ private bool IsThumbnail(Tiff tiff) // Current thumbnail identification relies on the SUBFILETYPE tag with a value of FileType.REDUCEDIMAGE. // This may need refinement in the future to include additional checks // (e.g., FileType.COMPRESSION is NONE, Image Dimensions). - return subFileTypeFieldValue != null && subFileTypeFieldValue.Length > 0 + return subFileTypeFieldValue != null && subFileTypeFieldValue.Length > 0 && (FileType)subFileTypeFieldValue[0].Value == FileType.REDUCEDIMAGE; } - private ReadOnlySpan PrepareByteArray(Image bmp, int[] raster, int width, int height) + private ReadOnlySpan PrepareByteArray(int[] raster, int width, int height,int bitsPerPixel) { - int stride = GetStride(bmp); + int stride = 4 * ((width * bitsPerPixel + 31) / 32); + byte[] bits = new byte[stride * height]; // If no extra padding exists, copy entire rows at once. @@ -2679,50 +3096,6 @@ private static List CreateAnyBitmaps(IEnumerable imagePaths) return bitmaps; } - private static MemoryStream CreateMultiFrameImage(IEnumerable images, ImageFormat imageFormat = ImageFormat.Tiff) - { - FindMaxWidthAndHeight(images, out int maxWidth, out int maxHeight); - - Image result = null; - for (int i = 0; i < images.Count(); i++) - { - if (i == 0) - { - result = LoadAndResizeImageSharp(images.ElementAt(i).GetBytes(), maxWidth, maxHeight, i); - } - else - { - if (result == null) - { - result = LoadAndResizeImageSharp(images.ElementAt(i).GetBytes(), maxWidth, maxHeight, i); - } - else - { - Image image = - LoadAndResizeImageSharp(images.ElementAt(i).GetBytes(), maxWidth, maxHeight, i); - _ = result.Frames.AddFrame(image.Frames.RootFrame); - } - } - } - - MemoryStream resultStream = null; - if (result != null) - { - resultStream = new MemoryStream(); - if (imageFormat == ImageFormat.Gif) - { - result.SaveAsGif(resultStream); - } - else - { - result.SaveAsTiff(resultStream); - } - } - - result?.Dispose(); - - return resultStream; - } private static void FindMaxWidthAndHeight(IEnumerable images, out int maxWidth, out int maxHeight) { @@ -2736,57 +3109,11 @@ private static void FindMaxWidthAndHeight(IEnumerable images, out int maxHeight = images.Select(img => img.Height).Max(); } - private static Image CloneAndResizeImageSharp( - Image source, int maxWidth, int maxHeight) - { - using Image image = - source.CloneAs(); - // Keep Image dimension the same - return ResizeWithPadToPng(image, maxWidth, maxHeight); - } - - private static Image LoadAndResizeImageSharp(byte[] bytes, - int maxWidth, int maxHeight, int index) - { - try - { - using var result = - Image.Load(bytes); - // Keep Image dimension the same - return ResizeWithPadToPng(result, maxWidth, maxHeight); - } - catch (Exception e) - { - throw new NotSupportedException($"Image index {index} cannot " + - $"be loaded. File format doesn't supported.", e); - } - } - - private static Image ResizeWithPadToPng( - Image result, int maxWidth, int maxHeight) - { - result.Mutate(img => img.Resize(new ResizeOptions - { - Size = new Size(maxWidth, maxHeight), - Mode = ResizeMode.BoxPad, - PadColor = SixLabors.ImageSharp.Color.Transparent - })); - - using var memoryStream = new MemoryStream(); - result.Save(memoryStream, new PngEncoder - { - TransparentColorMode = PngTransparentColorMode.Preserve - }); - _ = memoryStream.Seek(0, SeekOrigin.Begin); - - return Image.Load(memoryStream); - } - private int GetStride(Image source = null) { if (source == null) { - return 4 * (((Image.Width * Image.PixelType.BitsPerPixel) + 31) / 32); + return 4 * (((Width * BitsPerPixel) + 31) / 32); } else { @@ -2796,10 +3123,18 @@ private int GetStride(Image source = null) private IntPtr GetFirstPixelData() { - byte[] pixelBytes = new byte[Image.Width * Image.Height * Unsafe.SizeOf()]; - Image clonedImage = Image.CloneAs(); - clonedImage.CopyPixelDataTo(pixelBytes); - ConvertRGBAtoBGRA(pixelBytes, clonedImage.Width, clonedImage.Height); + var image = GetFirstInternalImage(); + + if(image is not Image) + { + image = image.CloneAs(); + } + + Image rgbaImage = (Image)image; + byte[] pixelBytes = new byte[rgbaImage.Width * rgbaImage.Height * Unsafe.SizeOf()]; + + rgbaImage.CopyPixelDataTo(pixelBytes); + ConvertRGBAtoBGRA(pixelBytes, rgbaImage.Width, rgbaImage.Height); IntPtr result = Marshal.AllocHGlobal(pixelBytes.Length); Marshal.Copy(pixelBytes, 0, result, pixelBytes.Length); @@ -2825,7 +3160,7 @@ private static void ConvertRGBAtoBGRA(byte[] data, int width, int height, int sa private Color GetPixelColor(int x, int y) { - switch (Image) + switch (GetFirstInternalImage()) { case Image imageAsFormat: return imageAsFormat[x, y]; @@ -2851,7 +3186,7 @@ private Color GetPixelColor(int x, int y) //CloneAs() is expensive! //Can throw out of memory exception, when this fucntion get called too much - using (Image converted = Image.CloneAs()) + using (Image converted = GetFirstInternalImage().CloneAs()) { return converted[x, y]; } @@ -2860,8 +3195,11 @@ private Color GetPixelColor(int x, int y) private void SetPixelColor(int x, int y, Color color) { - switch (Image) + switch (GetFirstInternalImage()) { + case Image imageAsFormat: + imageAsFormat[x, y] = color; + break; case Image imageAsFormat: imageAsFormat[x, y] = color; break; @@ -2877,27 +3215,64 @@ private void SetPixelColor(int x, int y, Color color) case Image imageAsFormat: imageAsFormat[x, y] = color; break; + case Image imageAsFormat: + imageAsFormat[x, y] = color; + break; + case Image imageAsFormat: + imageAsFormat[x, y] = color; + break; default: - (Image as Image)[x, y] = color; + (GetFirstInternalImage() as Image)[x, y] = color; break; } + IsDirty = true; } private void LoadAndResizeImage(AnyBitmap original, int width, int height) { + //this prevent case when original is changed before Lazy is loaded + Binary = original.Binary; + + _lazyImage = new Lazy>(() => + { + + var image = Image.Load(Binary); + image.Mutate(img => img.Resize(width, height)); + + //update Binary + using var memoryStream = new MemoryStream(); + image.Save(memoryStream, GetDefaultImageEncoder(image.Width, image.Height)); + Binary = memoryStream.ToArray(); + + return [image]; + }); + + ForceLoadLazyImage(); + } + + private IImageEncoder GetDefaultImageExportEncoder(ImageFormat format = ImageFormat.Default, int lossy = 100) + { + return format switch + { + ImageFormat.Jpeg => new JpegEncoder() + { + Quality = lossy, #if NET6_0_OR_GREATER - using var image = Image.Load(original.Binary); - IImageFormat format = image.Metadata.DecodedImageFormat; + ColorType = JpegEncodingColor.Rgb #else - using var image = Image.Load(original.Binary, out IImageFormat format); + ColorType = JpegColorType.Rgb #endif - image.Mutate(img => img.Resize(width, height)); - byte[] pixelBytes = new byte[image.Width * image.Height * Unsafe.SizeOf()]; - image.CopyPixelDataTo(pixelBytes); + }, + ImageFormat.Gif => new GifEncoder(), + ImageFormat.Png => new PngEncoder(), + ImageFormat.Webp => new WebpEncoder() { Quality = lossy }, + ImageFormat.Tiff => new TiffEncoder() + { + Compression = TiffCompression - Image = image.Clone(); - Binary = pixelBytes; - Format = format; + }, + _ => GetDefaultImageEncoder(Width, Height) + }; } private static ImageFormat GetImageFormat(string filename) @@ -2948,6 +3323,161 @@ object ICloneable.Clone() return this.Clone(); } - #endregion + /// + /// return BmpEncoder for small image and PngEncoder for large image + /// + /// + /// + /// + private static IImageEncoder GetDefaultImageEncoder(int imageWidth, int imageHeight) + { + return new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32, SupportTransparency = true }; + } + + private static void InternalSaveAsMultiPageTiff(IEnumerable images, Stream stream) + { + using (Tiff output = Tiff.ClientOpen("in-memory", "w", null, new NonClosingTiffStream(stream))) + { + foreach (var image in images) + { + int width = image.Width; + int height = image.Height; + int stride = width * 4; // RGBA => 4 bytes per pixel + + // Convert to byte[] in BGRA format as required by LibTiff + byte[] buffer = new byte[height * stride]; + + switch (image) + { + case Image imageAsFormat: + imageAsFormat.CopyPixelDataTo(buffer); + break; + case Image imageAsFormat: + imageAsFormat.CopyPixelDataTo(buffer); + break; + case Image imageAsFormat: + imageAsFormat.CopyPixelDataTo(buffer); + break; + case Image imageAsFormat: + imageAsFormat.CopyPixelDataTo(buffer); + break; + case Image imageAsFormat: + imageAsFormat.CopyPixelDataTo(buffer); + break; + default: + (image as Image).CopyPixelDataTo(buffer); + break; + } + + + //Note: TiffMetadata in current ImageSharp 3.1.8 is not good enough. but in the main branch of ImageSharp it looks good. + //TODO: revisit this TiffMetadata once release version of ImageSharp include new TiffMetadata implementation. + //TiffMetadata metadata = image.Metadata.GetTiffMetadata(); + + switch (image.Metadata.ResolutionUnits) + { + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.AspectRatio: + output.SetField(TiffTag.XRESOLUTION, image.Metadata.HorizontalResolution); + output.SetField(TiffTag.YRESOLUTION, image.Metadata.VerticalResolution); + output.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.NONE); + break; + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerInch: + output.SetField(TiffTag.XRESOLUTION, image.Metadata.HorizontalResolution); + output.SetField(TiffTag.YRESOLUTION, image.Metadata.VerticalResolution); + output.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.INCH); + break; + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerCentimeter: + output.SetField(TiffTag.XRESOLUTION, image.Metadata.HorizontalResolution); + output.SetField(TiffTag.YRESOLUTION, image.Metadata.VerticalResolution); + output.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.CENTIMETER); + break; + case SixLabors.ImageSharp.Metadata.PixelResolutionUnit.PixelsPerMeter: + output.SetField(TiffTag.XRESOLUTION, image.Metadata.HorizontalResolution * 100); + output.SetField(TiffTag.YRESOLUTION, image.Metadata.VerticalResolution * 100); + output.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.CENTIMETER); + break; + } + + + output.SetField(TiffTag.IMAGEWIDTH, width); + output.SetField(TiffTag.IMAGELENGTH, height); + output.SetField(TiffTag.SAMPLESPERPIXEL, 4); + output.SetField(TiffTag.BITSPERSAMPLE, 8, 8, 8, 8); + output.SetField(TiffTag.ORIENTATION, Orientation.TOPLEFT); + output.SetField(TiffTag.ROWSPERSTRIP, height); + output.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG); + output.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB); + output.SetField(TiffTag.COMPRESSION, Compression.LZW); // optional + output.SetField(TiffTag.EXTRASAMPLES, 1, new short[] { (short)ExtraSample.ASSOCALPHA }); + + // Write each scanline + for (int row = 0; row < height; row++) + { + int offset = row * stride; + output.WriteScanline(buffer, offset, row, 0); + } + + output.WriteDirectory(); // Next page + } + } + stream.Position = 0; + } + private static void InternalSaveAsMultiPageGif(IEnumerable images, Stream stream) + { + // Find the maximum dimensions to create a logical screen that can fit all frames. + int maxWidth = images.Max(f => f.Width); + int maxHeight = images.Max(f => f.Height); + + using var gif = images.First().Clone(ctx => ctx.Resize(new ResizeOptions + { + Size = new Size(maxWidth, maxHeight), + Mode = ResizeMode.BoxPad, // Pad to fit the target dimensions + PadColor = Color.Transparent, // Use transparent padding + Position = AnchorPositionMode.Center // Center the image within the frame + })); + + + foreach (var sourceImage in images.Skip(1)) + { + // Clone the source image and apply the more efficient Resize operation. + // This resizes the image to fit, pads the rest with a transparent color, + // and centers it, all in one step. + using (var resizedFrame = sourceImage.Clone(ctx => ctx.Resize(new ResizeOptions + { + Size = new Size(maxWidth, maxHeight), + Mode = ResizeMode.BoxPad, // Pad to fit the target dimensions + PadColor = Color.Transparent, // Use transparent padding + Position = AnchorPositionMode.Center // Center the image within the frame + }))) + { + // Add the correctly-sized new frame to the master GIF's frame collection. + gif.Frames.AddFrame(resizedFrame.Frames.RootFrame); + } + } + // Save the final result to the provided stream. + gif.SaveAsGif(stream); + stream.Position = 0; + } + +#endregion + + /// + /// Check if image is loaded (decoded) + /// + /// true if images is loaded (decoded) into the memory + [Browsable(false)] + [Bindable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsImageLoaded() + { + if(_lazyImage == null) + { + return false; + } + else + { + return _lazyImage.IsValueCreated; + } + } } } \ No newline at end of file diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/Color.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/Color.cs index 3600abe..2bf4492 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/Color.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/Color.cs @@ -11,6 +11,7 @@ namespace IronSoftware.Drawing /// public partial class Color { + private readonly bool _isKnownColor; /// /// Gets the alpha component value of this structure. @@ -36,6 +37,12 @@ public partial class Color /// The red component value of this . public byte R { get; internal set; } + /// + /// Gets a value indicating whether this structure is a predefined color. + /// + /// if this was created from a predefined color; otherwise, . + public bool IsKnownColor => _isKnownColor; + /// /// Construct a new . ///
Further Documentation:
Code Example
@@ -70,6 +77,8 @@ public Color(string colorcode) { throw NoConverterException(colorcode, null); } + + _isKnownColor = false; } /// @@ -86,6 +95,7 @@ public Color(int alpha, int red, int green, int blue) R = (byte)red; G = (byte)green; B = (byte)blue; + _isKnownColor = false; } /// @@ -101,6 +111,29 @@ public Color(int red, int green, int blue) R = (byte)red; G = (byte)green; B = (byte)blue; + _isKnownColor = false; + } + + /// + /// Construct a new from a packed ARGB integer value. + /// This constructor is intended for internal use when creating known colors, + /// ensuring the correct immutable state is set. + /// + /// + /// A 32-bit integer containing the alpha, red, green, and blue components in ARGB order. + /// The highest 8 bits represent the alpha component, followed by red, green, and blue. + /// + /// + /// Indicates whether the color is a predefined known color, used to control internal immutability behavior. + /// + internal Color(int argb, bool isKnownColor) + { + // This constructor is used internally to create known colors with the correct immutable flag. + A = (byte)(argb >> 24); + R = (byte)(argb >> 16); + G = (byte)(argb >> 8); + B = (byte)argb; + _isKnownColor = isKnownColor; } /// @@ -111,712 +144,712 @@ public Color(int red, int green, int blue) /// Gets a system-defined color that has an ARGB value of #F0F8FF. /// /// A representing a system-defined color. - public static readonly Color AliceBlue = new("#F0F8FF"); + public static readonly Color AliceBlue = FromKnownColor(KnownColor.AliceBlue); /// /// Gets a system-defined color that has an ARGB value of #FAEBD7. /// /// A representing a system-defined color. - public static readonly Color AntiqueWhite = new("#FAEBD7"); + public static readonly Color AntiqueWhite = FromKnownColor(KnownColor.AntiqueWhite); /// /// Gets a system-defined color that has an ARGB value of #00FFFF. /// /// A representing a system-defined color. - public static readonly Color Aqua = new("#00FFFF"); + public static readonly Color Aqua = FromKnownColor(KnownColor.Aqua); /// /// Gets a system-defined color that has an ARGB value of #7FFFD4. /// /// A representing a system-defined color. - public static readonly Color Aquamarine = new("#7FFFD4"); + public static readonly Color Aquamarine = FromKnownColor(KnownColor.Aquamarine); /// /// Gets a system-defined color that has an ARGB value of #F0FFFF. /// /// A representing a system-defined color. - public static readonly Color Azure = new("#F0FFFF"); + public static readonly Color Azure = FromKnownColor(KnownColor.Azure); /// /// Gets a system-defined color that has an ARGB value of #F5F5DC. /// /// A representing a system-defined color. - public static readonly Color Beige = new("#F5F5DC"); + public static readonly Color Beige = FromKnownColor(KnownColor.Beige); /// /// Gets a system-defined color that has an ARGB value of #FFE4C4. /// /// A representing a system-defined color. - public static readonly Color Bisque = new("#FFE4C4"); + public static readonly Color Bisque = FromKnownColor(KnownColor.Bisque); /// /// Gets a system-defined color that has an ARGB value of #000000. /// /// A representing a system-defined color. - public static readonly Color Black = new("#000000"); + public static readonly Color Black = FromKnownColor(KnownColor.Black); /// /// Gets a system-defined color that has an ARGB value of #FFEBCD. /// /// A representing a system-defined color. - public static readonly Color BlanchedAlmond = new("#FFEBCD"); + public static readonly Color BlanchedAlmond = FromKnownColor(KnownColor.BlanchedAlmond); /// /// Gets a system-defined color that has an ARGB value of #0000FF. /// /// A representing a system-defined color. - public static readonly Color Blue = new("#0000FF"); + public static readonly Color Blue = FromKnownColor(KnownColor.Blue); /// /// Gets a system-defined color that has an ARGB value of #8A2BE2. /// /// A representing a system-defined color. - public static readonly Color BlueViolet = new("#8A2BE2"); + public static readonly Color BlueViolet = FromKnownColor(KnownColor.BlueViolet); /// /// Gets a system-defined color that has an ARGB value of #A52A2A. /// /// A representing a system-defined color. - public static readonly Color Brown = new("#A52A2A"); + public static readonly Color Brown = FromKnownColor(KnownColor.Brown); /// /// Gets a system-defined color that has an ARGB value of #DEB887. /// /// A representing a system-defined color. - public static readonly Color BurlyWood = new("#DEB887"); + public static readonly Color BurlyWood = FromKnownColor(KnownColor.BurlyWood); /// /// Gets a system-defined color that has an ARGB value of #5F9EA0. /// /// A representing a system-defined color. - public static readonly Color CadetBlue = new("#5F9EA0"); + public static readonly Color CadetBlue = FromKnownColor(KnownColor.CadetBlue); /// /// Gets a system-defined color that has an ARGB value of #7FFF00. /// /// A representing a system-defined color. - public static readonly Color Chartreuse = new("#7FFF00"); + public static readonly Color Chartreuse = FromKnownColor(KnownColor.Chartreuse); /// /// Gets a system-defined color that has an ARGB value of #D2691E. /// /// A representing a system-defined color. - public static readonly Color Chocolate = new("#D2691E"); + public static readonly Color Chocolate = FromKnownColor(KnownColor.Chocolate); /// /// Gets a system-defined color that has an ARGB value of #FF7F50. /// /// A representing a system-defined color. - public static readonly Color Coral = new("#FF7F50"); + public static readonly Color Coral = FromKnownColor(KnownColor.Coral); /// /// Gets a system-defined color that has an ARGB value of #6495ED. /// /// A representing a system-defined color. - public static readonly Color CornflowerBlue = new("#6495ED"); + public static readonly Color CornflowerBlue = FromKnownColor(KnownColor.CornflowerBlue); /// /// Gets a system-defined color that has an ARGB value of #FFF8DC. /// /// A representing a system-defined color. - public static readonly Color Cornsilk = new("#FFF8DC"); + public static readonly Color Cornsilk = FromKnownColor(KnownColor.Cornsilk); /// /// Gets a system-defined color that has an ARGB value of #DC143C. /// /// A representing a system-defined color. - public static readonly Color Crimson = new("#DC143C"); + public static readonly Color Crimson = FromKnownColor(KnownColor.Crimson); /// /// Gets a system-defined color that has an ARGB value of #00FFFF. /// /// A representing a system-defined color. - public static readonly Color Cyan = new("#00FFFF"); + public static readonly Color Cyan = FromKnownColor(KnownColor.Cyan); /// /// Gets a system-defined color that has an ARGB value of #00008B. /// /// A representing a system-defined color. - public static readonly Color DarkBlue = new("#00008B"); + public static readonly Color DarkBlue = FromKnownColor(KnownColor.DarkBlue); /// /// Gets a system-defined color that has an ARGB value of #008B8B. /// /// A representing a system-defined color. - public static readonly Color DarkCyan = new("#008B8B"); + public static readonly Color DarkCyan = FromKnownColor(KnownColor.DarkCyan); /// /// Gets a system-defined color that has an ARGB value of #B8860B. /// /// A representing a system-defined color. - public static readonly Color DarkGoldenrod = new("#B8860B"); + public static readonly Color DarkGoldenrod = FromKnownColor(KnownColor.DarkGoldenrod); /// /// Gets a system-defined color that has an ARGB value of #A9A9A9. /// /// A representing a system-defined color. - public static readonly Color DarkGray = new("#A9A9A9"); + public static readonly Color DarkGray = FromKnownColor(KnownColor.DarkGray); /// /// Gets a system-defined color that has an ARGB value of #006400. /// /// A representing a system-defined color. - public static readonly Color DarkGreen = new("#006400"); + public static readonly Color DarkGreen = FromKnownColor(KnownColor.DarkGreen); /// /// Gets a system-defined color that has an ARGB value of #BDB76B. /// /// A representing a system-defined color. - public static readonly Color DarkKhaki = new("#BDB76B"); + public static readonly Color DarkKhaki = FromKnownColor(KnownColor.DarkKhaki); /// /// Gets a system-defined color that has an ARGB value of #8B008B. /// /// A representing a system-defined color. - public static readonly Color DarkMagenta = new("#8B008B"); + public static readonly Color DarkMagenta = FromKnownColor(KnownColor.DarkMagenta); /// /// Gets a system-defined color that has an ARGB value of #556B2F. /// /// A representing a system-defined color. - public static readonly Color DarkOliveGreen = new("#556B2F"); + public static readonly Color DarkOliveGreen = FromKnownColor(KnownColor.DarkOliveGreen); /// /// Gets a system-defined color that has an ARGB value of #FF8C00. /// /// A representing a system-defined color. - public static readonly Color DarkOrange = new("#FF8C00"); + public static readonly Color DarkOrange = FromKnownColor(KnownColor.DarkOrange); /// /// Gets a system-defined color that has an ARGB value of #9932CC. /// /// A representing a system-defined color. - public static readonly Color DarkOrchid = new("#9932CC"); + public static readonly Color DarkOrchid = FromKnownColor(KnownColor.DarkOrchid); /// /// Gets a system-defined color that has an ARGB value of #8B0000. /// /// A representing a system-defined color. - public static readonly Color DarkRed = new("#8B0000"); + public static readonly Color DarkRed = FromKnownColor(KnownColor.DarkRed); /// /// Gets a system-defined color that has an ARGB value of #E9967A. /// /// A representing a system-defined color. - public static readonly Color DarkSalmon = new("#E9967A"); + public static readonly Color DarkSalmon = FromKnownColor(KnownColor.DarkSalmon); /// /// Gets a system-defined color that has an ARGB value of #8FBC8B. /// /// A representing a system-defined color. - public static readonly Color DarkSeaGreen = new("#8FBC8B"); + public static readonly Color DarkSeaGreen = FromKnownColor(KnownColor.DarkSeaGreen); /// /// Gets a system-defined color that has an ARGB value of #483D8B. /// /// A representing a system-defined color. - public static readonly Color DarkSlateBlue = new("#483D8B"); + public static readonly Color DarkSlateBlue = FromKnownColor(KnownColor.DarkSlateBlue); /// /// Gets a system-defined color that has an ARGB value of #2F4F4F. /// /// A representing a system-defined color. - public static readonly Color DarkSlateGray = new("#2F4F4F"); + public static readonly Color DarkSlateGray = FromKnownColor(KnownColor.DarkSlateGray); /// /// Gets a system-defined color that has an ARGB value of #00CED1. /// /// A representing a system-defined color. - public static readonly Color DarkTurquoise = new("#00CED1"); + public static readonly Color DarkTurquoise = FromKnownColor(KnownColor.DarkTurquoise); /// /// Gets a system-defined color that has an ARGB value of #9400D3. /// /// A representing a system-defined color. - public static readonly Color DarkViolet = new("#9400D3"); + public static readonly Color DarkViolet = FromKnownColor(KnownColor.DarkViolet); /// /// Gets a system-defined color that has an ARGB value of #FF1493. /// /// A representing a system-defined color. - public static readonly Color DeepPink = new("#FF1493"); + public static readonly Color DeepPink = FromKnownColor(KnownColor.DeepPink); /// /// Gets a system-defined color that has an ARGB value of #00BFFF. /// /// A representing a system-defined color. - public static readonly Color DeepSkyBlue = new("#00BFFF"); + public static readonly Color DeepSkyBlue = FromKnownColor(KnownColor.DeepSkyBlue); /// /// Gets a system-defined color that has an ARGB value of #696969. /// /// A representing a system-defined color. - public static readonly Color DimGray = new("#696969"); + public static readonly Color DimGray = FromKnownColor(KnownColor.DimGray); /// /// Gets a system-defined color that has an ARGB value of #1E90FF. /// /// A representing a system-defined color. - public static readonly Color DodgerBlue = new("#1E90FF"); + public static readonly Color DodgerBlue = FromKnownColor(KnownColor.DodgerBlue); /// /// Gets a system-defined color that has an ARGB value of #B22222. /// /// A representing a system-defined color. - public static readonly Color Firebrick = new("#B22222"); + public static readonly Color Firebrick = FromKnownColor(KnownColor.Firebrick); /// /// Gets a system-defined color that has an ARGB value of #FFFAF0. /// /// A representing a system-defined color. - public static readonly Color FloralWhite = new("#FFFAF0"); + public static readonly Color FloralWhite = FromKnownColor(KnownColor.FloralWhite); /// /// Gets a system-defined color that has an ARGB value of #228B22. /// /// A representing a system-defined color. - public static readonly Color ForestGreen = new("#228B22"); + public static readonly Color ForestGreen = FromKnownColor(KnownColor.ForestGreen); /// /// Gets a system-defined color that has an ARGB value of #FF00FF. /// /// A representing a system-defined color. - public static readonly Color Fuchsia = new("#FF00FF"); + public static readonly Color Fuchsia = FromKnownColor(KnownColor.Fuchsia); /// /// Gets a system-defined color that has an ARGB value of #DCDCDC. /// /// A representing a system-defined color. - public static readonly Color Gainsboro = new("#DCDCDC"); + public static readonly Color Gainsboro = FromKnownColor(KnownColor.Gainsboro); /// /// Gets a system-defined color that has an ARGB value of #F8F8FF. /// /// A representing a system-defined color. - public static readonly Color GhostWhite = new("#F8F8FF"); + public static readonly Color GhostWhite = FromKnownColor(KnownColor.GhostWhite); /// /// Gets a system-defined color that has an ARGB value of #FFD700. /// /// A representing a system-defined color. - public static readonly Color Gold = new("#FFD700"); + public static readonly Color Gold = FromKnownColor(KnownColor.Gold); /// /// Gets a system-defined color that has an ARGB value of #DAA520. /// /// A representing a system-defined color. - public static readonly Color Goldenrod = new("#DAA520"); + public static readonly Color Goldenrod = FromKnownColor(KnownColor.Goldenrod); /// /// Gets a system-defined color that has an ARGB value of #808080. /// /// A representing a system-defined color. - public static readonly Color Gray = new("#808080"); + public static readonly Color Gray = FromKnownColor(KnownColor.Gray); /// /// Gets a system-defined color that has an ARGB value of #008000. /// /// A representing a system-defined color. - public static readonly Color Green = new("#008000"); + public static readonly Color Green = FromKnownColor(KnownColor.Green); /// /// Gets a system-defined color that has an ARGB value of #ADFF2F. /// /// A representing a system-defined color. - public static readonly Color GreenYellow = new("#ADFF2F"); + public static readonly Color GreenYellow = FromKnownColor(KnownColor.GreenYellow); /// /// Gets a system-defined color that has an ARGB value of #F0FFF0. /// /// A representing a system-defined color. - public static readonly Color Honeydew = new("#F0FFF0"); + public static readonly Color Honeydew = FromKnownColor(KnownColor.Honeydew); /// /// Gets a system-defined color that has an ARGB value of #FF69B4. /// /// A representing a system-defined color. - public static readonly Color HotPink = new("#FF69B4"); + public static readonly Color HotPink = FromKnownColor(KnownColor.HotPink); /// /// Gets a system-defined color that has an ARGB value of #CD5C5C. /// /// A representing a system-defined color. - public static readonly Color IndianRed = new("#CD5C5C"); + public static readonly Color IndianRed = FromKnownColor(KnownColor.IndianRed); /// /// Gets a system-defined color that has an ARGB value of #4B0082. /// /// A representing a system-defined color. - public static readonly Color Indigo = new("#4B0082"); + public static readonly Color Indigo = FromKnownColor(KnownColor.Indigo); /// /// Gets a system-defined color that has an ARGB value of #FFFFF0. /// /// A representing a system-defined color. - public static readonly Color Ivory = new("#FFFFF0"); + public static readonly Color Ivory = FromKnownColor(KnownColor.Ivory); /// /// Gets a system-defined color that has an ARGB value of #F0E68C. /// /// A representing a system-defined color. - public static readonly Color Khaki = new("#F0E68C"); + public static readonly Color Khaki = FromKnownColor(KnownColor.Khaki); /// /// Gets a system-defined color that has an ARGB value of #E6E6FA. /// /// A representing a system-defined color. - public static readonly Color Lavender = new("#E6E6FA"); + public static readonly Color Lavender = FromKnownColor(KnownColor.Lavender); /// /// Gets a system-defined color that has an ARGB value of #FFF0F5. /// /// A representing a system-defined color. - public static readonly Color LavenderBlush = new("#FFF0F5"); + public static readonly Color LavenderBlush = FromKnownColor(KnownColor.LavenderBlush); /// /// Gets a system-defined color that has an ARGB value of #7CFC00. /// /// A representing a system-defined color. - public static readonly Color LawnGreen = new("#7CFC00"); + public static readonly Color LawnGreen = FromKnownColor(KnownColor.LawnGreen); /// /// Gets a system-defined color that has an ARGB value of #FFFACD. /// /// A representing a system-defined color. - public static readonly Color LemonChiffon = new("#FFFACD"); + public static readonly Color LemonChiffon = FromKnownColor(KnownColor.LemonChiffon); /// /// Gets a system-defined color that has an ARGB value of #ADD8E6. /// /// A representing a system-defined color. - public static readonly Color LightBlue = new("#ADD8E6"); + public static readonly Color LightBlue = FromKnownColor(KnownColor.LightBlue); /// /// Gets a system-defined color that has an ARGB value of #F08080. /// /// A representing a system-defined color. - public static readonly Color LightCoral = new("#F08080"); + public static readonly Color LightCoral = FromKnownColor(KnownColor.LightCoral); /// /// Gets a system-defined color that has an ARGB value of #E0FFFF. /// /// A representing a system-defined color. - public static readonly Color LightCyan = new("#E0FFFF"); + public static readonly Color LightCyan = FromKnownColor(KnownColor.LightCyan); /// /// Gets a system-defined color that has an ARGB value of #FAFAD2. /// /// A representing a system-defined color. - public static readonly Color LightGoldenrodYellow = new("#FAFAD2"); + public static readonly Color LightGoldenrodYellow = FromKnownColor(KnownColor.LightGoldenrodYellow); /// /// Gets a system-defined color that has an ARGB value of #D3D3D3. /// /// A representing a system-defined color. - public static readonly Color LightGray = new("#D3D3D3"); + public static readonly Color LightGray = FromKnownColor(KnownColor.LightGray); /// /// Gets a system-defined color that has an ARGB value of #90EE90. /// /// A representing a system-defined color. - public static readonly Color LightGreen = new("#90EE90"); + public static readonly Color LightGreen = FromKnownColor(KnownColor.LightGreen); /// /// Gets a system-defined color that has an ARGB value of #FFB6C1. /// /// A representing a system-defined color. - public static readonly Color LightPink = new("#FFB6C1"); + public static readonly Color LightPink = FromKnownColor(KnownColor.LightPink); /// /// Gets a system-defined color that has an ARGB value of #FFA07A. /// /// A representing a system-defined color. - public static readonly Color LightSalmon = new("#FFA07A"); + public static readonly Color LightSalmon = FromKnownColor(KnownColor.LightSalmon); /// /// Gets a system-defined color that has an ARGB value of #20B2AA. /// /// A representing a system-defined color. - public static readonly Color LightSeaGreen = new("#20B2AA"); + public static readonly Color LightSeaGreen = FromKnownColor(KnownColor.LightSeaGreen); /// /// Gets a system-defined color that has an ARGB value of #87CEFA. /// /// A representing a system-defined color. - public static readonly Color LightSkyBlue = new("#87CEFA"); + public static readonly Color LightSkyBlue = FromKnownColor(KnownColor.LightSkyBlue); /// /// Gets a system-defined color that has an ARGB value of #778899. /// /// A representing a system-defined color. - public static readonly Color LightSlateGray = new("#778899"); + public static readonly Color LightSlateGray = FromKnownColor(KnownColor.LightSlateGray); /// /// Gets a system-defined color that has an ARGB value of #B0C4DE. /// /// A representing a system-defined color. - public static readonly Color LightSteelBlue = new("#B0C4DE"); + public static readonly Color LightSteelBlue = FromKnownColor(KnownColor.LightSteelBlue); /// /// Gets a system-defined color that has an ARGB value of #FFFFE0. /// /// A representing a system-defined color. - public static readonly Color LightYellow = new("#FFFFE0"); + public static readonly Color LightYellow = FromKnownColor(KnownColor.LightYellow); /// /// Gets a system-defined color that has an ARGB value of #00FF00. /// /// A representing a system-defined color. - public static readonly Color Lime = new("#00FF00"); + public static readonly Color Lime = FromKnownColor(KnownColor.Lime); /// /// Gets a system-defined color that has an ARGB value of #32CD32. /// /// A representing a system-defined color. - public static readonly Color LimeGreen = new("#32CD32"); + public static readonly Color LimeGreen = FromKnownColor(KnownColor.LimeGreen); /// /// Gets a system-defined color that has an ARGB value of #FAF0E6. /// /// A representing a system-defined color. - public static readonly Color Linen = new("#FAF0E6"); + public static readonly Color Linen = FromKnownColor(KnownColor.Linen); /// /// Gets a system-defined color that has an ARGB value of #FF00FF. /// /// A representing a system-defined color. - public static readonly Color Magenta = new("#FF00FF"); + public static readonly Color Magenta = FromKnownColor(KnownColor.Magenta); /// /// Gets a system-defined color that has an ARGB value of #800000. /// /// A representing a system-defined color. - public static readonly Color Maroon = new("#800000"); + public static readonly Color Maroon = FromKnownColor(KnownColor.Maroon); /// /// Gets a system-defined color that has an ARGB value of #66CDAA. /// /// A representing a system-defined color. - public static readonly Color MediumAquamarine = new("#66CDAA"); + public static readonly Color MediumAquamarine = FromKnownColor(KnownColor.MediumAquamarine); /// /// Gets a system-defined color that has an ARGB value of #0000CD. /// /// A representing a system-defined color. - public static readonly Color MediumBlue = new("#0000CD"); + public static readonly Color MediumBlue = FromKnownColor(KnownColor.MediumBlue); /// /// Gets a system-defined color that has an ARGB value of #BA55D3. /// /// A representing a system-defined color. - public static readonly Color MediumOrchid = new("#BA55D3"); + public static readonly Color MediumOrchid = FromKnownColor(KnownColor.MediumOrchid); /// /// Gets a system-defined color that has an ARGB value of #9370DB. /// /// A representing a system-defined color. - public static readonly Color MediumPurple = new("#9370DB"); + public static readonly Color MediumPurple = FromKnownColor(KnownColor.MediumPurple); /// /// Gets a system-defined color that has an ARGB value of #3CB371. /// /// A representing a system-defined color. - public static readonly Color MediumSeaGreen = new("#3CB371"); + public static readonly Color MediumSeaGreen = FromKnownColor(KnownColor.MediumSeaGreen); /// /// Gets a system-defined color that has an ARGB value of #7B68EE. /// /// A representing a system-defined color. - public static readonly Color MediumSlateBlue = new("#7B68EE"); + public static readonly Color MediumSlateBlue = FromKnownColor(KnownColor.MediumSlateBlue); /// /// Gets a system-defined color that has an ARGB value of #00FA9A. /// /// A representing a system-defined color. - public static readonly Color MediumSpringGreen = new("#00FA9A"); + public static readonly Color MediumSpringGreen = FromKnownColor(KnownColor.MediumSpringGreen); /// /// Gets a system-defined color that has an ARGB value of #48D1CC. /// /// A representing a system-defined color. - public static readonly Color MediumTurquoise = new("#48D1CC"); + public static readonly Color MediumTurquoise = FromKnownColor(KnownColor.MediumTurquoise); /// /// Gets a system-defined color that has an ARGB value of #C71585. /// /// A representing a system-defined color. - public static readonly Color MediumVioletRed = new("#C71585"); + public static readonly Color MediumVioletRed = FromKnownColor(KnownColor.MediumVioletRed); /// /// Gets a system-defined color that has an ARGB value of #191970. /// /// A representing a system-defined color. - public static readonly Color MidnightBlue = new("#191970"); + public static readonly Color MidnightBlue = FromKnownColor(KnownColor.MidnightBlue); /// /// Gets a system-defined color that has an ARGB value of #F5FFFA. /// /// A representing a system-defined color. - public static readonly Color MintCream = new("#F5FFFA"); + public static readonly Color MintCream = FromKnownColor(KnownColor.MintCream); /// /// Gets a system-defined color that has an ARGB value of #FFE4E1. /// /// A representing a system-defined color. - public static readonly Color MistyRose = new("#FFE4E1"); + public static readonly Color MistyRose = FromKnownColor(KnownColor.MistyRose); /// /// Gets a system-defined color that has an ARGB value of #FFE4B5. /// /// A representing a system-defined color. - public static readonly Color Moccasin = new("#FFE4B5"); + public static readonly Color Moccasin = FromKnownColor(KnownColor.Moccasin); /// /// Gets a system-defined color that has an ARGB value of #FFDEAD. /// /// A representing a system-defined color. - public static readonly Color NavajoWhite = new("#FFDEAD"); + public static readonly Color NavajoWhite = FromKnownColor(KnownColor.NavajoWhite); /// /// Gets a system-defined color that has an ARGB value of #000080. /// /// A representing a system-defined color. - public static readonly Color Navy = new("#000080"); + public static readonly Color Navy = FromKnownColor(KnownColor.Navy); /// /// Gets a system-defined color that has an ARGB value of #FDF5E6. /// /// A representing a system-defined color. - public static readonly Color OldLace = new("#FDF5E6"); + public static readonly Color OldLace = FromKnownColor(KnownColor.OldLace); /// /// Gets a system-defined color that has an ARGB value of #808000. /// /// A representing a system-defined color. - public static readonly Color Olive = new("#808000"); + public static readonly Color Olive = FromKnownColor(KnownColor.Olive); /// /// Gets a system-defined color that has an ARGB value of #6B8E23. /// /// A representing a system-defined color. - public static readonly Color OliveDrab = new("#6B8E23"); + public static readonly Color OliveDrab = FromKnownColor(KnownColor.OliveDrab); /// /// Gets a system-defined color that has an ARGB value of #FFA500. /// /// A representing a system-defined color. - public static readonly Color Orange = new("#FFA500"); + public static readonly Color Orange = FromKnownColor(KnownColor.Orange); /// /// Gets a system-defined color that has an ARGB value of #FF4500. /// /// A representing a system-defined color. - public static readonly Color OrangeRed = new("#FF4500"); + public static readonly Color OrangeRed = FromKnownColor(KnownColor.OrangeRed); /// /// Gets a system-defined color that has an ARGB value of #DA70D6. /// /// A representing a system-defined color. - public static readonly Color Orchid = new("#DA70D6"); + public static readonly Color Orchid = FromKnownColor(KnownColor.Orchid); /// /// Gets a system-defined color that has an ARGB value of #EEE8AA. /// /// A representing a system-defined color. - public static readonly Color PaleGoldenrod = new("#EEE8AA"); + public static readonly Color PaleGoldenrod = FromKnownColor(KnownColor.PaleGoldenrod); /// /// Gets a system-defined color that has an ARGB value of #98FB98. /// /// A representing a system-defined color. - public static readonly Color PaleGreen = new("#98FB98"); + public static readonly Color PaleGreen = FromKnownColor(KnownColor.PaleGreen); /// /// Gets a system-defined color that has an ARGB value of #AFEEEE. /// /// A representing a system-defined color. - public static readonly Color PaleTurquoise = new("#AFEEEE"); + public static readonly Color PaleTurquoise = FromKnownColor(KnownColor.PaleTurquoise); /// /// Gets a system-defined color that has an ARGB value of #DB7093. /// /// A representing a system-defined color. - public static readonly Color PaleVioletRed = new("#DB7093"); + public static readonly Color PaleVioletRed = FromKnownColor(KnownColor.PaleVioletRed); /// /// Gets a system-defined color that has an ARGB value of #FFEFD5. /// /// A representing a system-defined color. - public static readonly Color PapayaWhip = new("#FFEFD5"); + public static readonly Color PapayaWhip = FromKnownColor(KnownColor.PapayaWhip); /// /// Gets a system-defined color that has an ARGB value of #FFDAB9. /// /// A representing a system-defined color. - public static readonly Color PeachPuff = new("#FFDAB9"); + public static readonly Color PeachPuff = FromKnownColor(KnownColor.PeachPuff); /// /// Gets a system-defined color that has an ARGB value of #CD853F. /// /// A representing a system-defined color. - public static readonly Color Peru = new("#CD853F"); + public static readonly Color Peru = FromKnownColor(KnownColor.Peru); /// /// Gets a system-defined color that has an ARGB value of #FFC0CB. /// /// A representing a system-defined color. - public static readonly Color Pink = new("#FFC0CB"); + public static readonly Color Pink = FromKnownColor(KnownColor.Pink); /// /// Gets a system-defined color that has an ARGB value of #DDA0DD. /// /// A representing a system-defined color. - public static readonly Color Plum = new("#DDA0DD"); + public static readonly Color Plum = FromKnownColor(KnownColor.Plum); /// /// Gets a system-defined color that has an ARGB value of #B0E0E6. /// /// A representing a system-defined color. - public static readonly Color PowderBlue = new("#B0E0E6"); + public static readonly Color PowderBlue = FromKnownColor(KnownColor.PowderBlue); /// /// Gets a system-defined color that has an ARGB value of #800080. /// /// A representing a system-defined color. - public static readonly Color Purple = new("#800080"); + public static readonly Color Purple = FromKnownColor(KnownColor.Purple); /// /// Gets a system-defined color that has an ARGB value of #663399. /// /// A representing a system-defined color. - public static readonly Color RebeccaPurple = new("#663399"); + public static readonly Color RebeccaPurple = FromKnownColor(KnownColor.RebeccaPurple); /// /// Gets a system-defined color that has an ARGB value of #FF0000. /// /// A representing a system-defined color. - public static readonly Color Red = new("#FF0000"); + public static readonly Color Red = FromKnownColor(KnownColor.Red); /// /// Gets a system-defined color that has an ARGB value of #BC8F8F. /// /// A representing a system-defined color. - public static readonly Color RosyBrown = new("#BC8F8F"); + public static readonly Color RosyBrown = FromKnownColor(KnownColor.RosyBrown); /// /// Gets a system-defined color that has an ARGB value of #4169E1. /// /// A representing a system-defined color. - public static readonly Color RoyalBlue = new("#4169E1"); + public static readonly Color RoyalBlue = FromKnownColor(KnownColor.RoyalBlue); /// /// Gets a system-defined color that has an ARGB value of #8B4513. /// /// A representing a system-defined color. - public static readonly Color SaddleBrown = new("#8B4513"); + public static readonly Color SaddleBrown = FromKnownColor(KnownColor.SaddleBrown); /// /// Gets a system-defined color that has an ARGB value of #FA8072. /// /// A representing a system-defined color. - public static readonly Color Salmon = new("#FA8072"); + public static readonly Color Salmon = FromKnownColor(KnownColor.Salmon); /// /// Gets a system-defined color that has an ARGB value of #F4A460. /// /// A representing a system-defined color. - public static readonly Color SandyBrown = new("#F4A460"); + public static readonly Color SandyBrown = FromKnownColor(KnownColor.SandyBrown); /// /// Gets a system-defined color that has an ARGB value of #2E8B57. /// /// A representing a system-defined color. - public static readonly Color SeaGreen = new("#2E8B57"); + public static readonly Color SeaGreen = FromKnownColor(KnownColor.SeaGreen); /// /// Gets a system-defined color that has an ARGB value of #FFF5EE. /// /// A representing a system-defined color. - public static readonly Color SeaShell = new("#FFF5EE"); + public static readonly Color SeaShell = FromKnownColor(KnownColor.SeaShell); /// /// Gets a system-defined color that has an ARGB value of #A0522D. /// /// A representing a system-defined color. - public static readonly Color Sienna = new("#A0522D"); + public static readonly Color Sienna = FromKnownColor(KnownColor.Sienna); /// /// Gets a system-defined color that has an ARGB value of #C0C0C0. /// /// A representing a system-defined color. - public static readonly Color Silver = new("#C0C0C0"); + public static readonly Color Silver = FromKnownColor(KnownColor.Silver); /// /// Gets a system-defined color that has an ARGB value of #87CEEB. /// /// A representing a system-defined color. - public static readonly Color SkyBlue = new("#87CEEB"); + public static readonly Color SkyBlue = FromKnownColor(KnownColor.SkyBlue); /// /// Gets a system-defined color that has an ARGB value of #6A5ACD. /// /// A representing a system-defined color. - public static readonly Color SlateBlue = new("#6A5ACD"); + public static readonly Color SlateBlue = FromKnownColor(KnownColor.SlateBlue); /// /// Gets a system-defined color that has an ARGB value of #708090. /// /// A representing a system-defined color. - public static readonly Color SlateGray = new("#708090"); + public static readonly Color SlateGray = FromKnownColor(KnownColor.SlateGray); /// /// Gets a system-defined color that has an ARGB value of #FFFAFA. /// /// A representing a system-defined color. - public static readonly Color Snow = new("#FFFAFA"); + public static readonly Color Snow = FromKnownColor(KnownColor.Snow); /// /// Gets a system-defined color that has an ARGB value of #00FF7F. /// /// A representing a system-defined color. - public static readonly Color SpringGreen = new("#00FF7F"); + public static readonly Color SpringGreen = FromKnownColor(KnownColor.SpringGreen); /// /// Gets a system-defined color that has an ARGB value of #4682B4. /// /// A representing a system-defined color. - public static readonly Color SteelBlue = new("#4682B4"); + public static readonly Color SteelBlue = FromKnownColor(KnownColor.SteelBlue); /// /// Gets a system-defined color that has an ARGB value of #D2B48C. /// /// A representing a system-defined color. - public static readonly Color Tan = new("#D2B48C"); + public static readonly Color Tan = FromKnownColor(KnownColor.Tan); /// /// Gets a system-defined color that has an ARGB value of #008080. /// /// A representing a system-defined color. - public static readonly Color Teal = new("#008080"); + public static readonly Color Teal = FromKnownColor(KnownColor.Teal); /// /// Gets a system-defined color that has an ARGB value of #D2B48C. /// /// A representing a system-defined color. - public static readonly Color Thistle = new("#D8BFD8"); + public static readonly Color Thistle = FromKnownColor(KnownColor.Thistle); /// /// Gets a system-defined color that has an ARGB value of #FF6347. /// /// A representing a system-defined color. - public static readonly Color Tomato = new("#FF6347"); + public static readonly Color Tomato = FromKnownColor(KnownColor.Tomato); /// /// Gets a system-defined color that has an ARGB value of #00FFFFFF. /// /// A representing a system-defined color. - public static readonly Color Transparent = new("#00FFFFFF"); + public static readonly Color Transparent = FromKnownColor(KnownColor.Transparent); /// /// Gets a system-defined color that has an ARGB value of #40E0D0. /// /// A representing a system-defined color. - public static readonly Color Turquoise = new("#40E0D0"); + public static readonly Color Turquoise = FromKnownColor(KnownColor.Turquoise); /// /// Gets a system-defined color that has an ARGB value of #EE82EE. /// /// A representing a system-defined color. - public static readonly Color Violet = new("#EE82EE"); + public static readonly Color Violet = FromKnownColor(KnownColor.Violet); /// /// Gets a system-defined color that has an ARGB value of #F5DEB3. /// /// A representing a system-defined color. - public static readonly Color Wheat = new("#F5DEB3"); + public static readonly Color Wheat = FromKnownColor(KnownColor.Wheat); /// /// Gets a system-defined color that has an ARGB value of #FFFFFF. /// /// A representing a system-defined color. - public static readonly Color White = new("#FFFFFF"); + public static readonly Color White = FromKnownColor(KnownColor.White); /// /// Gets a system-defined color that has an ARGB value of #F5F5F5. /// /// A representing a system-defined color. - public static readonly Color WhiteSmoke = new("#F5F5F5"); + public static readonly Color WhiteSmoke = FromKnownColor(KnownColor.WhiteSmoke); /// /// Gets a system-defined color that has an ARGB value of #FFFF00. /// /// A representing a system-defined color. - public static readonly Color Yellow = new("#FFFF00"); + public static readonly Color Yellow = FromKnownColor(KnownColor.Yellow); /// /// Gets a system-defined color that has an ARGB value of #9ACD32. /// /// A representing a system-defined color. - public static readonly Color YellowGreen = new("#9ACD32"); + public static readonly Color YellowGreen = FromKnownColor(KnownColor.YellowGreen); /// /// Creates a structure from the specified 8-bit color values @@ -888,22 +921,52 @@ public static Color FromArgb(int argb) return new Color(colorCode); } + private static Color FromKnownColor(KnownColor kc) + { + return KnownColors.FromKnownColor(kc); + } + /// - /// Creates a structure from the specified name of a predefined color. - /// - /// - /// + /// Creates a structure from the name of a color. + /// + /// A string that is the name of a predefined color. + /// + /// The that this method creates. If the color name is found, + /// the corresponding known Color is returned; otherwise, a Color representing + /// transparent black (A=0, R=0, G=0, B=0) is returned. + /// + /// + /// + /// This method mimics the behavior of System.Drawing.Color.FromName for compatibility. + /// Unlike previous versions, this method no longer throws an exception for unknown color names. + /// + /// + /// The property will be set to only if + /// the name matches a predefined color in the KnownColor enumeration. + /// + /// + /// Color name comparison is case-insensitive. Both "Red" and "red" will return the same color. + /// + /// + /// + /// + /// Color red = Color.FromName("Red"); // Returns known red color, IsKnownColor = true + /// Color unknown = Color.FromName("Foo"); // Returns transparent black, IsKnownColor = false + /// + /// public static Color FromName(string name) { if (!string.IsNullOrEmpty(name)) { - if (KnownColors.ArgbByName.TryGetValue(name.ToLower(), out uint argb)) + // Lookup to get the KnownColor enum + if (KnownColors.KnownColorByName.TryGetValue(name.ToLower(), out KnownColor kc)) { - return FromArgb((int)argb); + return FromKnownColor(kc); } } - throw new InvalidOperationException($"{name} is unable to convert to {typeof(Color)} because it requires a suitable name."); + // Return a new Color instance that is not a "known" color. + return new Color(0, 0, 0, 0); } /// @@ -1034,7 +1097,7 @@ public static implicit operator SixLabors.ImageSharp.PixelFormats.Rgba32(Color c /// will automatically be casted to public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Bgra32 color) { - return new Color(color.R, color.G, color.B, color.A); + return new Color(color.A, color.R, color.G, color.B); } /// @@ -1064,7 +1127,7 @@ public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Rgb24 co /// is explicitly cast to a public static implicit operator SixLabors.ImageSharp.PixelFormats.Rgb24(Color color) { - return SixLabors.ImageSharp.Color.FromRgb(color.R, color.G, color.B); + return SixLabors.ImageSharp.Color.FromRgba(color.R, color.G, color.B, color.A); } /// @@ -1094,7 +1157,7 @@ public static implicit operator SixLabors.ImageSharp.PixelFormats.Bgr24(Color co /// will automatically be casted to public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Rgb48 color) { - return new Color(color.R, color.G, color.B); + return (Color)SixLabors.ImageSharp.Color.FromRgb((byte)(color.R >> 8), (byte)(color.G >> 8), (byte)(color.B >> 8)); } /// @@ -1104,7 +1167,9 @@ public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Rgb48 co /// is explicitly cast to a public static implicit operator SixLabors.ImageSharp.PixelFormats.Rgb48(Color color) { - return new SixLabors.ImageSharp.PixelFormats.Rgb48(color.R, color.G, color.B); + var result = new SixLabors.ImageSharp.PixelFormats.Rgb48(); + result.FromRgba64((SixLabors.ImageSharp.PixelFormats.Rgba64)color); + return result; } /// @@ -1144,7 +1209,7 @@ public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Abgr32 c /// is explicitly cast to a public static implicit operator SixLabors.ImageSharp.PixelFormats.Abgr32(Color color) { - return new SixLabors.ImageSharp.PixelFormats.Abgr32(color.R, color.G, color.B, color.A); + return SixLabors.ImageSharp.Color.FromRgba(color.R, color.G, color.B, color.A); } /// @@ -1164,7 +1229,7 @@ public static implicit operator Color(SixLabors.ImageSharp.PixelFormats.Argb32 c /// is explicitly cast to a public static implicit operator SixLabors.ImageSharp.PixelFormats.Argb32(Color color) { - return new SixLabors.ImageSharp.PixelFormats.Argb32(color.R, color.G, color.B, color.A); + return SixLabors.ImageSharp.Color.FromRgba(color.R, color.G, color.B, color.A); } /// diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColor.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColor.cs index 5c000bb..705ad81 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColor.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColor.cs @@ -143,40 +143,41 @@ public enum KnownColor Plum = 138, PowderBlue = 139, Purple = 140, - Red = 141, - RosyBrown = 142, - RoyalBlue = 143, - SaddleBrown = 144, - Salmon = 145, - SandyBrown = 146, - SeaGreen = 147, - SeaShell = 148, - Sienna = 149, - Silver = 150, - SkyBlue = 151, - SlateBlue = 152, - SlateGray = 153, - Snow = 154, - SpringGreen = 155, - SteelBlue = 156, - Tan = 157, - Teal = 158, - Thistle = 159, - Tomato = 160, - Turquoise = 161, - Violet = 162, - Wheat = 163, - White = 164, - WhiteSmoke = 165, - Yellow = 166, - YellowGreen = 167, - ButtonFace = 168, - ButtonHighlight = 169, - ButtonShadow = 170, - GradientActiveCaption = 171, - GradientInactiveCaption = 172, - MenuBar = 173, - MenuHighlight = 174 + RebeccaPurple = 141, + Red = 142, + RosyBrown = 143, + RoyalBlue = 144, + SaddleBrown = 145, + Salmon = 146, + SandyBrown = 147, + SeaGreen = 148, + SeaShell = 149, + Sienna = 150, + Silver = 151, + SkyBlue = 152, + SlateBlue = 153, + SlateGray = 154, + Snow = 155, + SpringGreen = 156, + SteelBlue = 157, + Tan = 158, + Teal = 159, + Thistle = 160, + Tomato = 161, + Turquoise = 162, + Violet = 163, + Wheat = 164, + White = 165, + WhiteSmoke = 166, + Yellow = 167, + YellowGreen = 168, + ButtonFace = 169, + ButtonHighlight = 170, + ButtonShadow = 171, + GradientActiveCaption = 172, + GradientInactiveCaption = 173, + MenuBar = 174, + MenuHighlight = 175 } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColors.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColors.cs index 307cc9b..8bb3153 100644 --- a/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColors.cs +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/KnownColors.cs @@ -147,40 +147,41 @@ internal class KnownColors 0xFFDDA0DD, /* 138 - Plum */ 0xFFB0E0E6, /* 139 - PowderBlue */ 0xFF800080, /* 140 - Purple */ - 0xFFFF0000, /* 141 - Red */ - 0xFFBC8F8F, /* 142 - RosyBrown */ - 0xFF4169E1, /* 143 - RoyalBlue */ - 0xFF8B4513, /* 144 - SaddleBrown */ - 0xFFFA8072, /* 145 - Salmon */ - 0xFFF4A460, /* 146 - SandyBrown */ - 0xFF2E8B57, /* 147 - SeaGreen */ - 0xFFFFF5EE, /* 148 - SeaShell */ - 0xFFA0522D, /* 149 - Sienna */ - 0xFFC0C0C0, /* 150 - Silver */ - 0xFF87CEEB, /* 151 - SkyBlue */ - 0xFF6A5ACD, /* 152 - SlateBlue */ - 0xFF708090, /* 153 - SlateGray */ - 0xFFFFFAFA, /* 154 - Snow */ - 0xFF00FF7F, /* 155 - SpringGreen */ - 0xFF4682B4, /* 156 - SteelBlue */ - 0xFFD2B48C, /* 157 - Tan */ - 0xFF008080, /* 158 - Teal */ - 0xFFD8BFD8, /* 159 - Thistle */ - 0xFFFF6347, /* 160 - Tomato */ - 0xFF40E0D0, /* 161 - Turquoise */ - 0xFFEE82EE, /* 162 - Violet */ - 0xFFF5DEB3, /* 163 - Wheat */ - 0xFFFFFFFF, /* 164 - White */ - 0xFFF5F5F5, /* 165 - WhiteSmoke */ - 0xFFFFFF00, /* 166 - Yellow */ - 0xFF9ACD32, /* 167 - YellowGreen */ - 0xFFECE9D8, /* 168 - ButtonFace */ - 0xFFFFFFFF, /* 169 - ButtonHighlight */ - 0xFFACA899, /* 170 - ButtonShadow */ - 0xFF3D95FF, /* 171 - GradientActiveCaption */ - 0xFF9DB9EB, /* 172 - GradientInactiveCaption */ - 0xFFECE9D8, /* 173 - MenuBar */ - 0xFF316AC5, /* 174 - MenuHighlight */ + 0xFF663399, /* 141 - RebeccaPurple */ + 0xFFFF0000, /* 142 - Red */ + 0xFFBC8F8F, /* 143 - RosyBrown */ + 0xFF4169E1, /* 144 - RoyalBlue */ + 0xFF8B4513, /* 145 - SaddleBrown */ + 0xFFFA8072, /* 146 - Salmon */ + 0xFFF4A460, /* 147 - SandyBrown */ + 0xFF2E8B57, /* 148 - SeaGreen */ + 0xFFFFF5EE, /* 149 - SeaShell */ + 0xFFA0522D, /* 150 - Sienna */ + 0xFFC0C0C0, /* 151 - Silver */ + 0xFF87CEEB, /* 152 - SkyBlue */ + 0xFF6A5ACD, /* 153 - SlateBlue */ + 0xFF708090, /* 154 - SlateGray */ + 0xFFFFFAFA, /* 155 - Snow */ + 0xFF00FF7F, /* 156 - SpringGreen */ + 0xFF4682B4, /* 157 - SteelBlue */ + 0xFFD2B48C, /* 158 - Tan */ + 0xFF008080, /* 159 - Teal */ + 0xFFD8BFD8, /* 160 - Thistle */ + 0xFFFF6347, /* 161 - Tomato */ + 0xFF40E0D0, /* 162 - Turquoise */ + 0xFFEE82EE, /* 163 - Violet */ + 0xFFF5DEB3, /* 164 - Wheat */ + 0xFFFFFFFF, /* 165 - White */ + 0xFFF5F5F5, /* 166 - WhiteSmoke */ + 0xFFFFFF00, /* 167 - Yellow */ + 0xFF9ACD32, /* 168 - YellowGreen */ + 0xFFECE9D8, /* 169 - ButtonFace */ + 0xFFFFFFFF, /* 170 - ButtonHighlight */ + 0xFFACA899, /* 171 - ButtonShadow */ + 0xFF3D95FF, /* 172 - GradientActiveCaption */ + 0xFF9DB9EB, /* 173 - GradientInactiveCaption */ + 0xFFECE9D8, /* 174 - MenuBar */ + 0xFF316AC5, /* 175 - MenuHighlight */ }; internal static string[] Names = { @@ -325,6 +326,7 @@ internal class KnownColors "Plum", "PowderBlue", "Purple", + "RebeccaPurple", "Red", "RosyBrown", "RoyalBlue", @@ -362,6 +364,7 @@ internal class KnownColors }; private static Dictionary _argbByName = null; private static Dictionary _nameByArgb = null; + private static Dictionary _knownColorByName = null; internal static Dictionary ArgbByName { @@ -397,20 +400,34 @@ internal static Dictionary NameByArgb } } + internal static Dictionary KnownColorByName + { + get + { + if (_knownColorByName == null) + { + _knownColorByName = new Dictionary(); + for (int i = 0; i < Names.Length; ++i) + { + // The key is the lowercased name for case-insensitive matching + _knownColorByName[Names[i].ToLower()] = (KnownColor)i; + } + } + return _knownColorByName; + } + } + public static Color FromKnownColor(KnownColor kc) { - Color c; short n = (short)kc; if ((n <= 0) || (n >= ArgbValues.Length)) { - c = Color.FromArgb(0); - } - else - { - c = Color.FromArgb((int)ArgbValues[n]); + // For invalid KnownColor enum values, return transparent black, which is not a "known" color. + return new Color(0, 0, 0, 0); } - return c; + // Create a color and set its immutable IsKnownColor flag to true. + return new Color((int)ArgbValues[n], true); } public static string GetName(short kc) diff --git a/IronSoftware.Drawing/IronSoftware.Drawing.Common/NonClosingTiffStream.cs b/IronSoftware.Drawing/IronSoftware.Drawing.Common/NonClosingTiffStream.cs new file mode 100644 index 0000000..5632951 --- /dev/null +++ b/IronSoftware.Drawing/IronSoftware.Drawing.Common/NonClosingTiffStream.cs @@ -0,0 +1,59 @@ +using BitMiracle.LibTiff.Classic; +using System; +using System.IO; + +namespace IronSoftware.Drawing +{ + internal class NonClosingTiffStream : TiffStream, IDisposable + { + private readonly Stream _stream; + private bool _disposed = false; + + public NonClosingTiffStream(Stream stream) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + public override int Read(object clientData, byte[] buffer, int offset, int count) + { + return _stream.Read(buffer, offset, count); + } + + public override void Write(object clientData, byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + } + + public override long Seek(object clientData, long offset, SeekOrigin origin) + { + return _stream.Seek(offset, origin); + } + + public override void Close(object clientData) + { + // Suppress automatic closing — manual control only + } + + public override long Size(object clientData) + { + return _stream.Length; + } + + /// + /// Manually closes the underlying stream when you are ready. + /// + public void CloseStream() + { + if (!_disposed) + { + _stream.Dispose(); + _disposed = true; + } + } + + public void Dispose() + { + CloseStream(); + } + } +} \ No newline at end of file diff --git a/NuGet/IronSoftware.Drawing.nuspec b/NuGet/IronSoftware.Drawing.nuspec index c5b4921..818dd7b 100644 --- a/NuGet/IronSoftware.Drawing.nuspec +++ b/NuGet/IronSoftware.Drawing.nuspec @@ -40,7 +40,9 @@ 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. - - Updates internal dependencies. + - Optimizes AnyBitmap memory usage. + - Updates Color.FromName to align with System.Drawing. + - Adds support for RebeccaPurple color. 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