84 lines
2.6 KiB
C#
84 lines
2.6 KiB
C#
using FrameProcessor.Domain;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Formats.Png;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using SixLabors.ImageSharp.Processing;
|
|
using SixLabors.ImageSharp.Processing.Processors.Quantization;
|
|
|
|
namespace FrameProcessor.ImagePipeline;
|
|
|
|
public sealed class ImagePipeline : IImagePipeline
|
|
{
|
|
public byte[] Process(Stream input, Frame frame)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(input);
|
|
ArgumentNullException.ThrowIfNull(frame);
|
|
|
|
var dither = DitheringRegistry.All[frame.Dithering];
|
|
var displayPalette = PaletteFactory.BuildDisplay(frame.Palette);
|
|
var devicePalette = PaletteFactory.BuildDevice(frame.Palette);
|
|
var oriented = frame.Resolution.ForOrientation(frame.Orientation);
|
|
|
|
using var image = Image.Load<Rgba32>(input);
|
|
|
|
image.Mutate(ctx => ctx
|
|
.Resize(new ResizeOptions
|
|
{
|
|
Size = new Size(oriented.Width, oriented.Height),
|
|
Mode = ResizeMode.Crop,
|
|
Position = AnchorPositionMode.Center,
|
|
})
|
|
.Dither(dither, displayPalette));
|
|
|
|
RemapDisplayToDevice(image, frame.Palette);
|
|
|
|
if (frame.Orientation == Orientation.Portrait)
|
|
{
|
|
image.Mutate(ctx => ctx.Rotate(RotateMode.Rotate90));
|
|
}
|
|
|
|
var encoder = new PngEncoder
|
|
{
|
|
ColorType = PngColorType.Palette,
|
|
BitDepth = SmallestBitDepthFor(frame.Palette.Count),
|
|
Quantizer = new PaletteQuantizer(devicePalette),
|
|
};
|
|
|
|
using var output = new MemoryStream();
|
|
image.SaveAsPng(output, encoder);
|
|
return output.ToArray();
|
|
}
|
|
|
|
private static void RemapDisplayToDevice(Image<Rgba32> image, IReadOnlyList<PaletteEntry> palette)
|
|
{
|
|
var map = new Dictionary<Rgba32, Rgba32>(palette.Count);
|
|
foreach (var entry in palette)
|
|
{
|
|
map[entry.DisplayColor.ToPixel<Rgba32>()] = entry.DeviceColor.ToPixel<Rgba32>();
|
|
}
|
|
|
|
image.ProcessPixelRows(accessor =>
|
|
{
|
|
for (var y = 0; y < accessor.Height; y++)
|
|
{
|
|
var row = accessor.GetRowSpan(y);
|
|
for (var x = 0; x < row.Length; x++)
|
|
{
|
|
if (map.TryGetValue(row[x], out var mapped))
|
|
{
|
|
row[x] = mapped;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private static PngBitDepth SmallestBitDepthFor(int paletteCount) => paletteCount switch
|
|
{
|
|
<= 2 => PngBitDepth.Bit1,
|
|
<= 4 => PngBitDepth.Bit2,
|
|
<= 16 => PngBitDepth.Bit4,
|
|
_ => PngBitDepth.Bit8,
|
|
};
|
|
}
|