Browse Source

Complete tests and fix issues

pull/2579/head
James Jackson-South 2 years ago
parent
commit
b31bd2337c
  1. 8
      src/ImageSharp/Formats/Cur/CurDecoderCore.cs
  2. 2
      src/ImageSharp/Formats/Cur/CurEncoder.cs
  3. 4
      src/ImageSharp/Formats/Cur/CurEncoderCore.cs
  4. 25
      src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
  5. 8
      src/ImageSharp/Formats/Ico/IcoDecoderCore.cs
  6. 4
      src/ImageSharp/Formats/Ico/IcoEncoder.cs
  7. 4
      src/ImageSharp/Formats/Ico/IcoEncoderCore.cs
  8. 26
      src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
  9. 81
      src/ImageSharp/Formats/Icon/IconDecoderCore.cs
  10. 72
      src/ImageSharp/Formats/Icon/IconEncoderCore.cs
  11. 31
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  12. 7
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  13. 19
      tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
  14. 26
      tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
  15. 139
      tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
  16. 26
      tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs

8
src/ImageSharp/Formats/Cur/CurDecoderCore.cs

@ -14,11 +14,17 @@ internal sealed class CurDecoderCore : IconDecoderCore
{
}
protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel)
protected override void SetFrameMetadata(
ImageFrameMetadata metadata,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
ReadOnlyMemory<Color>? colorTable)
{
CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata();
curFrameMetadata.FromIconDirEntry(entry);
curFrameMetadata.Compression = compression;
curFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
curFrameMetadata.ColorTable = colorTable;
}
}

2
src/ImageSharp/Formats/Cur/CurEncoder.cs

@ -11,7 +11,7 @@ public sealed class CurEncoder : QuantizingImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
CurEncoderCore encoderCore = new();
CurEncoderCore encoderCore = new(this);
encoderCore.Encode(image, stream, cancellationToken);
}
}

4
src/ImageSharp/Formats/Cur/CurEncoderCore.cs

@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Cur;
internal sealed class CurEncoderCore : IconEncoderCore
{
public CurEncoderCore()
: base(IconFileType.CUR)
public CurEncoderCore(QuantizingImageEncoder encoder)
: base(encoder, IconFileType.CUR)
{
}
}

25
src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

@ -3,6 +3,7 @@
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Cur;
@ -18,14 +19,14 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
{
}
private CurFrameMetadata(CurFrameMetadata metadata)
private CurFrameMetadata(CurFrameMetadata other)
{
this.Compression = metadata.Compression;
this.HotspotX = metadata.HotspotX;
this.HotspotY = metadata.HotspotY;
this.EncodingWidth = metadata.EncodingWidth;
this.EncodingHeight = metadata.EncodingHeight;
this.BmpBitsPerPixel = metadata.BmpBitsPerPixel;
this.Compression = other.Compression;
this.HotspotX = other.HotspotX;
this.HotspotY = other.HotspotY;
this.EncodingWidth = other.EncodingWidth;
this.EncodingHeight = other.EncodingHeight;
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
}
/// <summary>
@ -45,13 +46,13 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
/// <summary>
/// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte EncodingHeight { get; set; }
@ -61,6 +62,12 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
/// <summary>
/// Gets or sets the color table, if any.
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public CurFrameMetadata DeepClone() => new(this);

8
src/ImageSharp/Formats/Ico/IcoDecoderCore.cs

@ -14,11 +14,17 @@ internal sealed class IcoDecoderCore : IconDecoderCore
{
}
protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel)
protected override void SetFrameMetadata(
ImageFrameMetadata metadata,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
ReadOnlyMemory<Color>? colorTable)
{
IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata();
icoFrameMetadata.FromIconDirEntry(entry);
icoFrameMetadata.Compression = compression;
icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
icoFrameMetadata.ColorTable = colorTable;
}
}

4
src/ImageSharp/Formats/Ico/IcoEncoder.cs

@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Formats.Ico;
/// <summary>
/// Image encoder for writing an image to a stream as a Windows Icon.
/// </summary>
public sealed class IcoEncoder : ImageEncoder
public sealed class IcoEncoder : QuantizingImageEncoder
{
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
IcoEncoderCore encoderCore = new();
IcoEncoderCore encoderCore = new(this);
encoderCore.Encode(image, stream, cancellationToken);
}
}

4
src/ImageSharp/Formats/Ico/IcoEncoderCore.cs

@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Ico;
internal sealed class IcoEncoderCore : IconEncoderCore
{
public IcoEncoderCore()
: base(IconFileType.ICO)
public IcoEncoderCore(QuantizingImageEncoder encoder)
: base(encoder, IconFileType.ICO)
{
}
}

26
src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs

@ -3,6 +3,7 @@
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Ico;
@ -18,12 +19,17 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
{
}
private IcoFrameMetadata(IcoFrameMetadata metadata)
private IcoFrameMetadata(IcoFrameMetadata other)
{
this.Compression = metadata.Compression;
this.EncodingWidth = metadata.EncodingWidth;
this.EncodingHeight = metadata.EncodingHeight;
this.BmpBitsPerPixel = metadata.BmpBitsPerPixel;
this.Compression = other.Compression;
this.EncodingWidth = other.EncodingWidth;
this.EncodingHeight = other.EncodingHeight;
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
if (other.ColorTable?.Length > 0)
{
this.ColorTable = other.ColorTable.Value.ToArray();
}
}
/// <summary>
@ -33,13 +39,13 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
/// <summary>
/// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
/// </summary>
public byte EncodingHeight { get; set; }
@ -49,6 +55,12 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
/// <summary>
/// Gets or sets the color table, if any.
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public IcoFrameMetadata DeepClone() => new(this);

81
src/ImageSharp/Formats/Icon/IconDecoderCore.cs

@ -31,10 +31,16 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
List<(Image<TPixel> Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.entries.Length);
List<(Image<TPixel> Image, IconFrameCompression Compression, int Index)> decodedEntries
= new((int)Math.Min(this.entries.Length, this.Options.MaxFrames));
for (int i = 0; i < this.entries.Length; i++)
{
if (i == this.Options.MaxFrames)
{
break;
}
ref IconDirEntry entry = ref this.entries[i];
// If we hit the end of the stream we should break.
@ -69,6 +75,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
Image<TPixel> result = new(this.Options.Configuration, metadata, decodedEntries.Select(x =>
{
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
ReadOnlyMemory<Color>? colorTable = null;
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
ImageFrame<TPixel> source = x.Image.Frames.RootFrameUnsafe;
for (int y = 0; y < source.Height; y++)
@ -88,11 +95,22 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
}
else
{
bmpMetadata = x.Image.Metadata.GetBmpMetadata();
bitsPerPixel = bmpMetadata.BitsPerPixel;
BmpMetadata meta = x.Image.Metadata.GetBmpMetadata();
bitsPerPixel = meta.BitsPerPixel;
colorTable = meta.ColorTable;
if (x.Index == 0)
{
bmpMetadata = meta;
}
}
this.SetFrameMetadata(target.Metadata, this.entries[x.Index], x.Compression, bitsPerPixel);
this.SetFrameMetadata(
target.Metadata,
this.entries[x.Index],
x.Compression,
bitsPerPixel,
colorTable);
x.Image.Dispose();
@ -122,11 +140,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
ImageMetadata metadata = new();
ImageFrameMetadata[] frames = new ImageFrameMetadata[this.fileHeader.Count];
BmpMetadata? bmpMetadata = null;
PngMetadata? pngMetadata = null;
ImageFrameMetadata[] frames = new ImageFrameMetadata[Math.Min(this.fileHeader.Count, this.Options.MaxFrames)];
int bpp = 0;
for (int i = 0; i < frames.Length; i++)
{
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
ReadOnlyMemory<Color>? colorTable = null;
ref IconDirEntry entry = ref this.entries[i];
// If we hit the end of the stream we should break.
@ -149,25 +170,65 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
// Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result.
ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken);
frames[i] = new();
if (!isPng)
ImageFrameMetadata frameMetadata = new();
if (isPng)
{
if (i == 0)
{
pngMetadata = temp.Metadata.GetPngMetadata();
}
frameMetadata.SetFormatMetadata(PngFormat.Instance, temp.FrameMetadataCollection[0].GetPngMetadata());
}
else
{
bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel;
BmpMetadata meta = temp.Metadata.GetBmpMetadata();
bitsPerPixel = meta.BitsPerPixel;
colorTable = meta.ColorTable;
if (i == 0)
{
bmpMetadata = meta;
}
}
bpp = Math.Max(bpp, (int)bitsPerPixel);
this.SetFrameMetadata(frames[i], this.entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel);
frames[i] = frameMetadata;
this.SetFrameMetadata(
frames[i],
this.entries[i],
isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp,
bitsPerPixel,
colorTable);
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
// which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft.
this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height));
}
// Copy the format specific metadata to the image.
if (bmpMetadata != null)
{
metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata);
}
if (pngMetadata != null)
{
metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata);
}
return new(new(bpp), this.Dimensions, metadata, frames);
}
protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel);
protected abstract void SetFrameMetadata(
ImageFrameMetadata metadata,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
ReadOnlyMemory<Color>? colorTable);
[MemberNotNull(nameof(entries))]
protected void ReadHeader(Stream stream)

72
src/ImageSharp/Formats/Icon/IconEncoderCore.cs

@ -13,12 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Icon;
internal abstract class IconEncoderCore : IImageEncoderInternals
{
private readonly QuantizingImageEncoder encoder;
private readonly IconFileType iconFileType;
private IconDir fileHeader;
private EncodingFrameMetadata[]? entries;
protected IconEncoderCore(IconFileType iconFileType)
=> this.iconFileType = iconFileType;
protected IconEncoderCore(QuantizingImageEncoder encoder, IconFileType iconFileType)
{
this.encoder = encoder;
this.iconFileType = iconFileType;
}
public void Encode<TPixel>(
Image<TPixel> image,
@ -71,14 +75,7 @@ internal abstract class IconEncoderCore : IImageEncoderInternals
{
IconFrameCompression.Bmp => new BmpEncoder()
{
// We don't have access to the palette in the metadata so we need to quantize the image
// using a new one generated from the pixel data.
Quantizer = encodingMetadata.Entry.BitCount <= 8
? new WuQuantizer(new()
{
MaxColors = encodingMetadata.Entry.ColorCount
})
: null,
Quantizer = this.GetQuantizer(encodingMetadata),
ProcessedAlphaMask = true,
UseDoubleHeight = true,
SkipFileHeader = true,
@ -122,28 +119,65 @@ internal abstract class IconEncoderCore : IImageEncoderInternals
image.Frames.Select(i =>
{
IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry());
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry());
}).ToArray(),
IconFileType.CUR =>
image.Frames.Select(i =>
{
CurFrameMetadata metadata = i.Metadata.GetCurMetadata();
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry());
return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry());
}).ToArray(),
_ => throw new NotSupportedException(),
};
}
internal sealed class EncodingFrameMetadata(
IconFrameCompression compression,
BmpBitsPerPixel bmpBitsPerPixel,
IconDirEntry iconDirEntry)
private IQuantizer? GetQuantizer(EncodingFrameMetadata metadata)
{
private IconDirEntry iconDirEntry = iconDirEntry;
if (metadata.Entry.BitCount > 8)
{
return null;
}
if (this.encoder.Quantizer is not null)
{
return this.encoder.Quantizer;
}
if (metadata.ColorTable is null)
{
return new WuQuantizer(new()
{
MaxColors = metadata.Entry.ColorCount
});
}
// Don't dither if we have a palette. We want to preserve as much information as possible.
return new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null });
}
internal sealed class EncodingFrameMetadata
{
private IconDirEntry iconDirEntry;
public EncodingFrameMetadata(
IconFrameCompression compression,
BmpBitsPerPixel bmpBitsPerPixel,
ReadOnlyMemory<Color>? colorTable,
IconDirEntry iconDirEntry)
{
this.Compression = compression;
this.BmpBitsPerPixel = compression == IconFrameCompression.Png
? BmpBitsPerPixel.Pixel32
: bmpBitsPerPixel;
this.ColorTable = colorTable;
this.iconDirEntry = iconDirEntry;
}
public IconFrameCompression Compression { get; }
public IconFrameCompression Compression { get; set; } = compression;
public BmpBitsPerPixel BmpBitsPerPixel { get; }
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = bmpBitsPerPixel;
public ReadOnlyMemory<Color>? ColorTable { get; set; }
public ref IconDirEntry Entry => ref this.iconDirEntry;
}

31
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -345,9 +345,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
uint frameCount = 0;
ImageMetadata metadata = new();
List<ImageFrameMetadata> framesMetadata = [];
PngMetadata pngMetadata = metadata.GetPngMetadata();
this.currentStream = stream;
FrameControl? lastFrameControl = null;
FrameControl? currentFrameControl = null;
Span<byte> buffer = stackalloc byte[20];
this.currentStream.Skip(8);
@ -400,7 +401,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
}
lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break;
case PngChunkType.FrameData:
if (frameCount == this.maxFrames)
@ -413,22 +415,35 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
goto EOF;
}
if (lastFrameControl is null)
if (currentFrameControl is null)
{
PngThrowHelper.ThrowMissingFrameControl();
}
InitializeFrameMetadata(framesMetadata, currentFrameControl.Value);
// Skip sequence number
this.currentStream.Skip(4);
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Data:
// Spec says tRNS must be before IDAT so safe to exit.
if (this.colorMetadataOnly)
{
goto EOF;
}
pngMetadata.AnimateRootFrame = currentFrameControl != null;
currentFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
if (framesMetadata.Count == 0)
{
InitializeFrameMetadata(framesMetadata, currentFrameControl.Value);
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
}
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Palette:
@ -515,7 +530,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata, framesMetadata);
}
finally
{
@ -680,6 +695,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.scanline = this.configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
}
private static void InitializeFrameMetadata(List<ImageFrameMetadata> imageFrameMetadata, FrameControl currentFrameControl)
{
ImageFrameMetadata meta = new();
PngFrameMetadata frameMetadata = meta.GetPngMetadata();
frameMetadata.FromChunk(currentFrameControl);
imageFrameMetadata.Add(meta);
}
/// <summary>
/// Calculates the correct number of bits per pixel for the given color type.
/// </summary>

7
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -62,9 +62,10 @@ public class PaletteQuantizer : IQuantizer
{
Guard.NotNull(options, nameof(options));
// Always use the palette length over options since the palette cannot be reduced.
TPixel[] palette = new TPixel[this.colorPalette.Length];
Color.ToPixel(this.colorPalette.Span, palette.AsSpan());
// If the palette is larger than the max colors then we need to trim it down.
// treat the buffer as FILO.
TPixel[] palette = new TPixel[Math.Min(options.MaxColors, this.colorPalette.Length)];
Color.ToPixel(this.colorPalette.Span[..palette.Length], palette.AsSpan());
return new PaletteQuantizer<TPixel>(configuration, options, palette, this.transparentIndex);
}
}

19
tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Tests.TestImages.Cur;
@ -17,9 +19,11 @@ public class CurDecoderTests
{
using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
// TODO: Assert metadata, frame count, etc
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
Assert.Equal(image.Width, meta.EncodingWidth);
Assert.Equal(image.Height, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
[Theory]
@ -28,9 +32,10 @@ public class CurDecoderTests
public void CurDecoder_Decode2(TestImageProvider<Rgba32> provider)
{
using Image<Rgba32> image = provider.GetImage(CurDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
// TODO: Assert metadata, frame count, etc
CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata();
Assert.Equal(image.Width, meta.EncodingWidth);
Assert.Equal(image.Height, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
}

26
tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs

@ -3,6 +3,7 @@
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using static SixLabors.ImageSharp.Tests.TestImages.Cur;
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur;
@ -10,23 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur;
[Trait("Format", "Cur")]
public class CurEncoderTests
{
private static CurEncoder CurEncoder => new();
public static readonly TheoryData<string> Files = new()
{
{ WindowsMouse },
};
private static CurEncoder Encoder => new();
[Theory]
[MemberData(nameof(Files))]
public void Encode(string imagePath)
[WithFile(CurReal, PixelTypes.Rgba32)]
[WithFile(WindowsMouse, PixelTypes.Rgba32)]
public void CanRoundTripEncoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using Image<TPixel> image = provider.GetImage(CurDecoder.Instance);
using MemoryStream memStream = new();
input.Save(memStream, CurEncoder);
image.DebugSaveMultiFrame(provider);
image.Save(memStream, Encoder);
memStream.Seek(0, SeekOrigin.Begin);
CurDecoder.Instance.Decode(new(), memStream);
using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false);
encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance);
}
}

139
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Tests.TestImages.Ico;
@ -17,9 +19,9 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSaveMultiFrame(provider);
// TODO: Assert metadata, frame count, etc
Assert.Equal(10, image.Frames.Count);
}
[Theory]
@ -45,9 +47,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
// TODO: Assert metadata, frame count, etc
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel1, meta.BmpBitsPerPixel);
}
[Theory]
@ -74,9 +83,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
// TODO: Assert metadata, frame count, etc
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel24, meta.BmpBitsPerPixel);
}
[Theory]
@ -103,9 +119,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
// TODO: Assert metadata, frame count, etc
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
[Theory]
@ -131,9 +154,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
// TODO: Assert metadata, frame count, etc
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel4, meta.BmpBitsPerPixel);
}
[Theory]
@ -151,7 +181,8 @@ public class IcoDecoderTests
[WithFile(Bpp8Size5x5, PixelTypes.Rgba32)]
[WithFile(Bpp8Size6x6, PixelTypes.Rgba32)]
[WithFile(Bpp8Size7x7, PixelTypes.Rgba32)]
[WithFile(Bpp8Size8x8, PixelTypes.Rgba32)]
// [WithFile(Bpp8Size8x8, PixelTypes.Rgba32)] This is actually 24 bit.
[WithFile(Bpp8Size9x9, PixelTypes.Rgba32)]
[WithFile(Bpp8TranspNotSquare, PixelTypes.Rgba32)]
[WithFile(Bpp8TranspPartial, PixelTypes.Rgba32)]
@ -159,9 +190,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
// TODO: Assert metadata, frame count, etc
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel8, meta.BmpBitsPerPixel);
}
[Theory]
@ -174,10 +212,6 @@ public class IcoDecoderTests
=> Assert.Throws<NotSupportedException>(() =>
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
// TODO: Assert metadata, frame count, etc
});
[Theory]
@ -186,9 +220,16 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
// TODO: Assert metadata, frame count, etc
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Png, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
[Theory]
@ -199,9 +240,9 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
Assert.True(image.Frames.Count > 1);
// TODO: Assert metadata, frame count, etc
image.DebugSaveMultiFrame(provider);
}
[Theory]
@ -215,9 +256,46 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
Assert.True(image.Frames.Count > 1);
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<Rgba32> frame = image.Frames[i];
IcoFrameMetadata meta = frame.Metadata.GetIcoMetadata();
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
image.DebugSaveMultiFrame(provider);
}
[Theory]
[WithFile(MultiSizeA, PixelTypes.Rgba32)]
[WithFile(MultiSizeB, PixelTypes.Rgba32)]
[WithFile(MultiSizeC, PixelTypes.Rgba32)]
[WithFile(MultiSizeD, PixelTypes.Rgba32)]
[WithFile(MultiSizeE, PixelTypes.Rgba32)]
[WithFile(MultiSizeF, PixelTypes.Rgba32)]
public void MultiSize_CanDecodeSingleFrame(TestImageProvider<Rgba32> provider)
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance, new() { MaxFrames = 1 });
Assert.Single(image.Frames);
}
// TODO: Assert metadata, frame count, etc
[Theory]
[InlineData(MultiSizeA)]
[InlineData(MultiSizeB)]
[InlineData(MultiSizeC)]
[InlineData(MultiSizeD)]
[InlineData(MultiSizeE)]
[InlineData(MultiSizeF)]
public void MultiSize_CanIdentifySingleFrame(string imagePath)
{
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(new() { MaxFrames = 1 }, stream);
Assert.Single(imageInfo.FrameMetadataCollection);
}
[Theory]
@ -229,9 +307,9 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
Assert.True(image.Frames.Count > 1);
// TODO: Assert metadata, frame count, etc
image.DebugSaveMultiFrame(provider);
}
[Theory]
@ -240,8 +318,15 @@ public class IcoDecoderTests
{
using Image<Rgba32> image = provider.GetImage(IcoDecoder.Instance);
image.DebugSaveMultiFrame(provider, extension: "png");
image.DebugSave(provider);
IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata();
int expectedWidth = image.Width >= 256 ? 0 : image.Width;
int expectedHeight = image.Height >= 256 ? 0 : image.Height;
// TODO: Assert metadata, frame count, etc
Assert.Equal(expectedWidth, meta.EncodingWidth);
Assert.Equal(expectedHeight, meta.EncodingHeight);
Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
}
}

26
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs

@ -3,6 +3,7 @@
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using static SixLabors.ImageSharp.Tests.TestImages.Ico;
namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico;
@ -10,23 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico;
[Trait("Format", "Icon")]
public class IcoEncoderTests
{
private static IcoEncoder CurEncoder => new();
public static readonly TheoryData<string> Files = new()
{
{ Flutter },
};
private static IcoEncoder Encoder => new();
[Theory]
[MemberData(nameof(Files))]
public void Encode(string imagePath)
[WithFile(Flutter, PixelTypes.Rgba32)]
public void CanRoundTripEncoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using Image<TPixel> image = provider.GetImage(IcoDecoder.Instance);
using MemoryStream memStream = new();
input.Save(memStream, CurEncoder);
image.DebugSaveMultiFrame(provider);
image.Save(memStream, Encoder);
memStream.Seek(0, SeekOrigin.Begin);
IcoDecoder.Instance.Decode(new(), memStream);
using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false);
// Despite preservation of the palette. The process can still be lossy
encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance);
}
}

Loading…
Cancel
Save