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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CI/job_templates/build_drawing_libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ jobs:
inputs:
packageType: 'sdk'
version: '8.x'
# Six Labors license (required by ImageSharp v4 / ImageSharp.Drawing v3 build-time check on the net8.0 target)
- task: DownloadSecureFile@1
name: sixLaborsLicense
displayName: 'Download Six Labors license file'
inputs:
secureFile: 'sixlabors.lic'
# Build
- task: DotNetCoreCLI@2
displayName: Restore Solution NuGet Packages
Expand All @@ -58,6 +64,7 @@ jobs:
--no-restore
--verbosity normal
--property:AssemblyVersion=$(finalAssemblyVersion)
--property:SixLaborsLicenseFile=$(sixLaborsLicense.secureFilePath)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major: License validation is only enforced in Release; PR CI can't prove it

Finding: PR validation builds Debug (CI/pr-validation.yml:39). In that configuration the Six Labors ValidateLicenseTask runs with ContinueOnError="$(Configuration.StartsWith('Debug'))", i.e. it only warns. Only the release pipeline builds Release (CI/azure-pipelines-build.yml:31), and there an invalid, expired or wrong-product license fails the build.

Why it matters: a green PR check doesn't show that sixlabors.lic is a valid license for ImageSharp 4 and ImageSharp.Drawing 3. The first place it can fail is the release build. Devs running local Release builds also need a sixlabors.lic somewhere under the tree (the target globs **/sixlabors.lic), and .gitignore doesn't exclude it, so it could get committed by accident.

Suggested change: run one Release build of this branch (or check the Debug build log for a license warning) before merging. Add sixlabors.lic to .gitignore.

versioningScheme: byEnvVar
versionEnvVar: AssemblyVersion
# Publish Pipeline Artifacts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BitMiracle.LibTiff.Classic;
using FluentAssertions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
Expand Down Expand Up @@ -1155,7 +1156,11 @@ public void Resize_ShouldPreserveDepthForRepresentableFormats()
Assert.Equal(24, new AnyBitmap(rgb24, 20, 20).BitsPerPixel);

string path64 = "dw40_tmp64.png";
#if NET8_0_OR_GREATER
using (var img64 = new Image<Rgba64>(30, 30)) { img64.Save(path64, new SixLabors.ImageSharp.Formats.Png.PngEncoder { ColorType = SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha, BitDepth = SixLabors.ImageSharp.Formats.Png.PngBitDepth.Bit16 }); }
#else
using (var img64 = new Image<Rgba64>(30, 30)) { img64.SaveAsPng(path64); }
#endif
try
{
var rgba64 = AnyBitmap.FromFile(path64);
Expand All @@ -1168,6 +1173,82 @@ public void Resize_ShouldPreserveDepthForRepresentableFormats()
}
}

[TheoryWithAutomaticDisplayName]
[InlineData("A8", "GrayscaleWithAlpha", "Bit8", 16)]
[InlineData("L8", "Grayscale", "Bit8", 8)]
[InlineData("L16", "Grayscale", "Bit16", 16)]
[InlineData("La16", "GrayscaleWithAlpha", "Bit8", 16)]
[InlineData("La32", "GrayscaleWithAlpha", "Bit16", 32)]
[InlineData("Rgb24", "Rgb", "Bit8", 24)]
[InlineData("Bgr24", "Rgb", "Bit8", 24)]
[InlineData("Rgb48", "Rgb", "Bit16", 48)]
[InlineData("Rgba32", "RgbWithAlpha", "Bit8", 32)]
[InlineData("Bgra32", "RgbWithAlpha", "Bit8", 32)]
[InlineData("Rgba64", "RgbWithAlpha", "Bit16", 64)]
[InlineData("RgbaVector", "RgbWithAlpha", "Bit16", 64)]
[InlineData("Bgr565", "RgbWithAlpha", "Bit8", 32)]
public void ExportPng_ShouldChooseColorTypeAndBitDepthFromPixelType(
string pixelType, string expectedColorType, string expectedBitDepth, int expectedBitsPerPixel)
{
using Image source = pixelType switch
{
"A8" => new Image<A8>(4, 4),
"L8" => new Image<L8>(4, 4),
"L16" => new Image<L16>(4, 4),
"La16" => new Image<La16>(4, 4),
"La32" => new Image<La32>(4, 4),
"Rgb24" => new Image<Rgb24>(4, 4),
"Bgr24" => new Image<Bgr24>(4, 4),
"Rgb48" => new Image<Rgb48>(4, 4),
"Rgba32" => new Image<Rgba32>(4, 4),
"Bgra32" => new Image<Bgra32>(4, 4),
"Rgba64" => new Image<Rgba64>(4, 4),
"RgbaVector" => new Image<RgbaVector>(4, 4),
"Bgr565" => new Image<SixLabors.ImageSharp.PixelFormats.Bgr565>(4, 4),
_ => throw new ArgumentOutOfRangeException(nameof(pixelType))
};
AnyBitmap bitmap = source;

byte[] png = bitmap.ExportBytes(AnyBitmap.ImageFormat.Png);

using (Image reloaded = Image.Load(png))
{
var pngMetadata = reloaded.Metadata.GetPngMetadata();
Assert.Equal(expectedColorType, pngMetadata.ColorType.ToString());
Assert.Equal(expectedBitDepth, pngMetadata.BitDepth.ToString());
}
Assert.Equal(expectedBitsPerPixel, AnyBitmap.FromBytes(png).BitsPerPixel);
}

[TheoryWithAutomaticDisplayName]
[InlineData("Palette", "Bit8")]
[InlineData("Grayscale", "Bit1")]
[InlineData("Grayscale", "Bit16")]
public void ExportPng_ShouldKeepSourcePngColorTypeAndBitDepthWhenReencoding(string colorType, string bitDepth)
{
byte[] sourcePng;
using (var source = new Image<Rgba32>(8, 8, new Rgba32(255, 255, 255, 255)))
using (var stream = new MemoryStream())
{
source.Save(stream, new SixLabors.ImageSharp.Formats.Png.PngEncoder
{
ColorType = (SixLabors.ImageSharp.Formats.Png.PngColorType)Enum.Parse(typeof(SixLabors.ImageSharp.Formats.Png.PngColorType), colorType),
BitDepth = (SixLabors.ImageSharp.Formats.Png.PngBitDepth)Enum.Parse(typeof(SixLabors.ImageSharp.Formats.Png.PngBitDepth), bitDepth)
});
sourcePng = stream.ToArray();
}

// RotateFlip decodes and re-encodes, so the output goes through the PNG encoder
// instead of returning the original bytes.
var rotated = AnyBitmap.FromBytes(sourcePng).RotateFlip(AnyBitmap.RotateMode.Rotate180, AnyBitmap.FlipMode.None);
byte[] png = rotated.ExportBytes(AnyBitmap.ImageFormat.Png);

using Image reloaded = Image.Load(png);
var pngMetadata = reloaded.Metadata.GetPngMetadata();
Assert.Equal(colorType, pngMetadata.ColorType.ToString());
Assert.Equal(bitDepth, pngMetadata.BitDepth.ToString());
}

[FactWithAutomaticDisplayName]
public void Resize_ShouldPreserveFrameCountForMultiPageTiff()
{
Expand Down Expand Up @@ -1296,7 +1377,11 @@ public void Redact_ShouldRedactRegionWithColor()
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.White);
image.Save(memoryStream, new SixLabors.ImageSharp.Formats.Bmp.BmpEncoder()
{
#if NET8_0_OR_GREATER
BitsPerPixel = SixLabors.ImageSharp.Formats.Bmp.BmpBitsPerPixel.Bit32,
#else
BitsPerPixel = SixLabors.ImageSharp.Formats.Bmp.BmpBitsPerPixel.Pixel32,
#endif
SupportTransparency = true
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,22 @@ public void Cast_ImageSharp_Color_from_Color()
Assert.Equal(0, red.G);
Assert.Equal(0, red.B);

#if NET8_0_OR_GREATER
imgColor = SixLabors.ImageSharp.Color.FromPixel(new SixLabors.ImageSharp.PixelFormats.Rgba32(0, 255, 0, 255));
#else
imgColor = SixLabors.ImageSharp.Color.FromRgba(0, 255, 0, 255);
#endif
Color green = imgColor;
Assert.Equal(255, green.A);
Assert.Equal(0, green.R);
Assert.Equal(255, green.G);
Assert.Equal(0, green.B);

#if NET8_0_OR_GREATER
imgColor = SixLabors.ImageSharp.Color.FromPixel(new SixLabors.ImageSharp.PixelFormats.Rgb24(0, 0, 255));
#else
imgColor = SixLabors.ImageSharp.Color.FromRgb(0, 0, 255);
#endif
Color blue = imgColor;
Assert.Equal(255, blue.A);
Assert.Equal(0, blue.R);
Expand Down Expand Up @@ -322,7 +330,11 @@ public void Cast_ImageSharp_Color_to_Color()
[FactWithAutomaticDisplayName]
public void Cast_ImageSharp_Rgba32_from_Color()
{
#if NET8_0_OR_GREATER
SixLabors.ImageSharp.PixelFormats.Rgba32 imgColor = SixLabors.ImageSharp.Color.Red.ToPixel<SixLabors.ImageSharp.PixelFormats.Rgba32>();
#else
SixLabors.ImageSharp.PixelFormats.Rgba32 imgColor = SixLabors.ImageSharp.Color.Red;
#endif
Color red = imgColor;
Assert.Equal(255, red.A);
Assert.Equal(255, red.R);
Expand Down Expand Up @@ -367,7 +379,11 @@ public void Cast_ImageSharp_Rgba32_to_Color()
[FactWithAutomaticDisplayName]
public void Cast_ImageSharp_Rgb24_from_Color()
{
#if NET8_0_OR_GREATER
SixLabors.ImageSharp.PixelFormats.Rgb24 imgColor = SixLabors.ImageSharp.Color.Red.ToPixel<SixLabors.ImageSharp.PixelFormats.Rgb24>();
#else
SixLabors.ImageSharp.PixelFormats.Rgb24 imgColor = SixLabors.ImageSharp.Color.Red;
#endif
Color red = imgColor;
Assert.Equal(255, red.R);
Assert.Equal(0, red.G);
Expand Down Expand Up @@ -470,7 +486,11 @@ public void Cast_ImageSharp_Rgb48_to_Color()
[FactWithAutomaticDisplayName]
public void Cast_ImageSharp_Rgba64_from_Color()
{
#if NET8_0_OR_GREATER
SixLabors.ImageSharp.PixelFormats.Rgba64 imgColor = SixLabors.ImageSharp.Color.Red.ToPixel<SixLabors.ImageSharp.PixelFormats.Rgba64>();
#else
SixLabors.ImageSharp.PixelFormats.Rgba64 imgColor = SixLabors.ImageSharp.Color.Red;
#endif
Color red = imgColor;
Assert.Equal(255, red.R);
Assert.Equal(0, red.G);
Expand Down
50 changes: 48 additions & 2 deletions IronSoftware.Drawing/IronSoftware.Drawing.Common/AnyBitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,11 @@ public AnyBitmap(int width, int height, Color backgroundColor = null)
var image = new Image<Rgba32>(width, height);
if (backgroundColor != null)
{
#if NET8_0_OR_GREATER
image.Mutate(context => context.Paint(canvas => canvas.Fill(new SolidBrush(backgroundColor))));
#else
image.Mutate(context => context.Fill(backgroundColor));
#endif
}
return [image];
});
Expand Down Expand Up @@ -1524,7 +1528,11 @@ public static AnyBitmap Redact(
Image image = Image.Load(bitmap.Binary);
Rectangle rectangle = Rectangle;
var brush = new SolidBrush(color);
#if NET8_0_OR_GREATER
image.Mutate(ctx => ctx.Paint(canvas => canvas.Fill(brush, rectangle)));
#else
image.Mutate(ctx => ctx.Fill(brush, rectangle));
#endif

// Redact fills a region but leaves the rest of the image untouched, so it carries the
// source's declared color depth as the (decoupled, in-memory) BitsPerPixel label,
Expand Down Expand Up @@ -3735,14 +3743,14 @@ private IImageEncoder GetDefaultImageExportEncoder(ImageFormat format = ImageFor
ImageFormat.Jpeg => new JpegEncoder()
{
Quality = lossy,
#if NET6_0_OR_GREATER
#if NET6_0_OR_GREATER && !NET8_0_OR_GREATER
ColorType = JpegEncodingColor.Rgb
#else
ColorType = JpegColorType.Rgb
#endif
},
ImageFormat.Gif => new GifEncoder(),
ImageFormat.Png => new PngEncoder(),
ImageFormat.Png => GetDefaultPngEncoder(),
ImageFormat.Webp => new WebpEncoder() { Quality = lossy },
ImageFormat.Tiff => new TiffEncoder()
{
Expand All @@ -3753,6 +3761,40 @@ private IImageEncoder GetDefaultImageExportEncoder(ImageFormat format = ImageFor
};
}

private PngEncoder GetDefaultPngEncoder()
{
#if NET8_0_OR_GREATER
// ImageSharp 3 picked the PNG color type/bit depth as: source PngMetadata if the image
// came from a PNG, otherwise a suggestion based on the pixel type. ImageSharp 4 still
// honors the source PngMetadata, but for any other source it falls back to 8-bit
// RgbWithAlpha. Reproduce the ImageSharp 3 behavior so net8 output matches net6.
Image image = GetFirstInternalImage();
if (image.Metadata.DecodedImageFormat is PngFormat)
{
return new PngEncoder();
}

// Same per-pixel-type table as ImageSharp 3's encoder (anything not listed fell back
// to 8-bit RgbWithAlpha there too).
(PngColorType colorType, PngBitDepth bitDepth) = image switch
{
Image<A8> => (PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8),
Image<L8> => (PngColorType.Grayscale, PngBitDepth.Bit8),
Image<L16> => (PngColorType.Grayscale, PngBitDepth.Bit16),
Image<La16> => (PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8),
Image<La32> => (PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16),
Image<Rgb24> or Image<Bgr24> => (PngColorType.Rgb, PngBitDepth.Bit8),
Image<Rgb48> => (PngColorType.Rgb, PngBitDepth.Bit16),
Image<Rgba64> or Image<RgbaVector> => (PngColorType.RgbWithAlpha, PngBitDepth.Bit16),
_ => (PngColorType.RgbWithAlpha, PngBitDepth.Bit8)
};

return new PngEncoder { ColorType = colorType, BitDepth = bitDepth };
#else
return new PngEncoder();
#endif
}

private static ImageFormat GetImageFormat(string filename)
{
if (string.IsNullOrEmpty(filename))
Expand Down Expand Up @@ -3809,7 +3851,11 @@ object ICloneable.Clone()
/// <returns></returns>
private static IImageEncoder GetDefaultImageEncoder(int imageWidth, int imageHeight)
{
#if NET8_0_OR_GREATER
return new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Bit32, SupportTransparency = true };
#else
return new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32, SupportTransparency = true };
#endif
}

private static void InternalSaveAsMultiPageTiff(IEnumerable<Image> images, Stream stream)
Expand Down
Loading
Loading