// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; public class MagickReferenceDecoder : ImageDecoder { private readonly IImageFormat imageFormat; private readonly bool validate; public MagickReferenceDecoder(IImageFormat imageFormat) : this(imageFormat, true) { } public MagickReferenceDecoder(IImageFormat imageFormat, bool validate) { this.imageFormat = imageFormat; this.validate = validate; } public static MagickReferenceDecoder Png { get; } = new MagickReferenceDecoder(PngFormat.Instance); public static MagickReferenceDecoder Bmp { get; } = new MagickReferenceDecoder(BmpFormat.Instance); public static MagickReferenceDecoder Jpeg { get; } = new MagickReferenceDecoder(JpegFormat.Instance); public static MagickReferenceDecoder Tiff { get; } = new MagickReferenceDecoder(TiffFormat.Instance); public static MagickReferenceDecoder WebP { get; } = new MagickReferenceDecoder(WebpFormat.Instance); protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { ImageMetadata metadata = new(); Configuration configuration = options.Configuration; BmpReadDefines bmpReadDefines = new() { IgnoreFileSize = !this.validate }; PngReadDefines pngReadDefines = new() { IgnoreCrc = !this.validate }; MagickReadSettings settings = new() { FrameCount = (int)options.MaxFrames }; settings.SetDefines(bmpReadDefines); settings.SetDefines(pngReadDefines); using MagickImageCollection magickImageCollection = new(stream, settings); List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) { ImageFrame frame = new(configuration, (int)magicFrame.Width, (int)magicFrame.Height); framesList.Add(frame); MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); FromRgba32Bytes(configuration, data, framePixels); } else if (magicFrame.Depth is 14 or 16 or 32) { if (this.imageFormat is PngFormat png) { metadata.GetPngMetadata().BitDepth = PngBitDepth.Bit16; } ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); FromRgba64Bytes(configuration, bytes, framePixels); } else { throw new InvalidOperationException(); } } return ReferenceCodecUtilities.EnsureDecodedMetadata(new(configuration, metadata, framesList), this.imageFormat); } protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.Decode(options, stream, cancellationToken); protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { using Image image = this.Decode(options, stream, cancellationToken); ImageMetadata metadata = image.Metadata; return new(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) { PixelType = metadata.GetDecodedPixelTypeInfo() }; } private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); foreach (Memory m in destinationGroup) { Span destBuffer = m.Span; PixelOperations.Instance.FromRgba32( configuration, sourcePixels[..destBuffer.Length], destBuffer); sourcePixels = sourcePixels[destBuffer.Length..]; } } private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { foreach (Memory m in destinationGroup) { Span destBuffer = m.Span; PixelOperations.Instance.FromRgba64Bytes( configuration, rgbaBytes, destBuffer, destBuffer.Length); rgbaBytes = rgbaBytes[(destBuffer.Length * 8)..]; } } }