/// <summary> /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> private void Write8BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image, Span <byte> colorPalette) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); ReadOnlySpan <TPixel> quantizedColors = quantized.Palette.Span; PixelOperations <TPixel> .Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast <byte, Bgra32>(colorPalette)); Span <uint> colorPaletteAsUInt = MemoryMarshal.Cast <byte, uint>(colorPalette); for (int i = 0; i < colorPaletteAsUInt.Length; i++) { colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. } stream.Write(colorPalette); for (int y = image.Height - 1; y >= 0; y--) { ReadOnlySpan <byte> pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) { stream.WriteByte(0); } } }
public void ApplyQuantizationDither <TFrameQuantizer, TPixel>( ref TFrameQuantizer quantizer, ImageFrame <TPixel> source, IndexedImageFrame <TPixel> destination, Rectangle bounds) where TFrameQuantizer : struct, IQuantizer <TPixel> where TPixel : unmanaged, IPixel <TPixel> { if (this == default) { ThrowDefaultInstance(); } int spread = CalculatePaletteSpread(destination.Palette.Length); float scale = quantizer.Options.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { ReadOnlySpan <TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); Span <byte> destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length); for (int x = 0; x < sourceRow.Length; x++) { TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale); destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); } } }
private void EncodeGlobal <TPixel>(Image <TPixel> image, IndexedImageFrame <TPixel> quantized, int transparencyIndex, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { // The palette quantizer can reuse the same pixel map across multiple frames // since the palette is unchanging. This allows a reduction of memory usage across // multi frame gifs using a global palette. EuclideanPixelMap <TPixel> pixelMap = default; bool pixelMapSet = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame <TPixel> frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); this.WriteImageDescriptor(frame, false, stream); if (i == 0) { this.WriteImageData(quantized, stream); } else { if (!pixelMapSet) { pixelMapSet = true; pixelMap = new EuclideanPixelMap <TPixel>(this.configuration, quantized.Palette); } using var paletteFrameQuantizer = new PaletteFrameQuantizer <TPixel>(this.configuration, this.quantizer.Options, pixelMap); using IndexedImageFrame <TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } } }
/// <summary> /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); this.width = image.Width; this.height = image.Height; ImageMetadata metadata = image.Metadata; PngMetadata pngMetadata = metadata.GetPngMetadata(); PngEncoderOptionsHelpers.AdjustOptions <TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); IndexedImageFrame <TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); stream.Write(PngConstants.HeaderBytes); this.WriteHeaderChunk(stream); this.WritePaletteChunk(stream, quantized); this.WriteTransparencyChunk(stream, pngMetadata); this.WritePhysicalChunk(stream, metadata); this.WriteGammaChunk(stream); this.WriteExifChunk(stream, metadata); this.WriteTextChunks(stream, pngMetadata); this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); this.WriteEndChunk(stream); stream.Flush(); quantized?.Dispose(); }
/// <summary> /// Returns a span of bytes in the correct format for the PictEncoder /// </summary> /// <typeparam name="TPixel"></typeparam> /// <param name="image"></param> /// <param name="quantized"></param> /// <param name="y"></param> /// <returns></returns> private ReadOnlySpan <byte> GetScanLine <TPixel>(Image <TPixel> image, IndexedImageFrame <TPixel> quantized, int y) where TPixel : unmanaged, IPixel <TPixel> { if (quantized == null && options.IsIndexed) { throw new Exception("Quantized image expected for indexed images"); } if (quantized != null) { return(quantized.GetPixelRowSpan(y)); } var pixels = image.GetPixelRowSpan(y); var pixelBytes = (int)options.PictBpp / 8; using IMemoryOwner <byte> row = memoryAllocator.Allocate <byte>(pixelBytes * image.Width); Span <byte> rowSpan = row.Memory.Span; switch (options.PictBpp) { case PictBpp.Bit16: PixelOperations <TPixel> .Instance.ToBgra5551Bytes(configuration, pixels, rowSpan, pixels.Length); break; case PictBpp.Bit32: default: PixelOperations <TPixel> .Instance.ToBgra32Bytes(configuration, pixels, rowSpan, pixels.Length); break; } return(rowSpan); }
public TiffPaletteWriter( ImageFrame <TPixel> image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) : base(image, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); DebugGuard.NotNull(configuration, nameof(configuration)); DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); this.BitsPerPixel = bitsPerPixel; this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; this.colorPaletteSize = this.maxColors * 3; this.colorPaletteBytes = this.colorPaletteSize * 2; using IQuantizer <TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer <TPixel>(this.Configuration, new QuantizerOptions() { MaxColors = this.maxColors }); this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); this.AddColorMapTag(); }
/// <summary> /// Returns the index of the most transparent color in the palette. /// </summary> /// <param name="quantized">The quantized frame.</param> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <returns> /// The <see cref="int"/>. /// </returns> private int GetTransparentIndex <TPixel>(IndexedImageFrame <TPixel> quantized) where TPixel : unmanaged, IPixel <TPixel> { // Transparent pixels are much more likely to be found at the end of a palette. int index = -1; ReadOnlySpan <TPixel> paletteSpan = quantized.Palette.Span; using IMemoryOwner <Rgba32> rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate <Rgba32>(paletteSpan.Length); Span <Rgba32> rgbaSpan = rgbaOwner.GetSpan(); PixelOperations <TPixel> .Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan);
/// <summary> /// Returns a PaletteEntry array from a quantized image /// </summary> /// <typeparam name="TPixel"></typeparam> /// <param name="quantized">Quantized image</param> /// <returns>Array of Palette Entries</returns> private PaletteEntry[] GetPalette <TPixel>(IndexedImageFrame <TPixel> quantized) where TPixel : unmanaged, IPixel <TPixel> { ReadOnlySpan <TPixel> sourcePalette = quantized.Palette.Span; int paletteLength = sourcePalette.Length; PaletteEntry[] destinationPalette = new PaletteEntry[paletteLength]; for (int i = 0; i < paletteLength; i++) { Rgba32 rgba = new Rgba32(); sourcePalette[i].ToRgba32(ref rgba); destinationPalette[i] = new PaletteEntry(rgba.A, rgba.R, rgba.G, rgba.B); } return(destinationPalette); }
private void EncodeLocal <TPixel>(Image <TPixel> image, IndexedImageFrame <TPixel> quantized, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { ImageFrame <TPixel> previousFrame = null; GifFrameMetadata previousMeta = null; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame <TPixel> frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) { // Allow each frame to be encoded at whatever color depth the frame designates if set. if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength && frameMetadata.ColorTableLength > 0) { var options = new QuantizerOptions { Dither = this.quantizer.Options.Dither, DitherScale = this.quantizer.Options.DitherScale, MaxColors = frameMetadata.ColorTableLength }; using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration, options); quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } else { using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration); quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; previousMeta = frameMetadata; } }
public void SinglePixelTransparent() { Configuration config = Configuration.Default; var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); using var image = new Image <Rgba32>(config, 1, 1, default(Rgba32)); ImageFrame <Rgba32> frame = image.Frames.RootFrame; using IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(config); using IndexedImageFrame <Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelBufferSpan().Length); Assert.Equal(default, result.Palette.Span[0]);
/// <summary> /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); this.width = image.Width; this.height = image.Height; ImageMetadata metadata = image.Metadata; PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); PngEncoderOptionsHelpers.AdjustOptions <TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); Image <TPixel> clonedImage = null; bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear; if (clearTransparency) { clonedImage = image.Clone(); ClearTransparentPixels(clonedImage); } IndexedImageFrame <TPixel> quantized = this.CreateQuantizedImage(image, clonedImage); stream.Write(PngConstants.HeaderBytes); this.WriteHeaderChunk(stream); this.WriteGammaChunk(stream); this.WritePaletteChunk(stream, quantized); this.WriteTransparencyChunk(stream, pngMetadata); this.WritePhysicalChunk(stream, metadata); this.WriteExifChunk(stream, metadata); this.WriteTextChunks(stream, pngMetadata); this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream); this.WriteEndChunk(stream); stream.Flush(); quantized?.Dispose(); clonedImage?.Dispose(); }
public void SinglePixelOpaque() { Configuration config = Configuration.Default; var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); using var image = new Image <Rgba32>(config, 1, 1, Color.Black); ImageFrame <Rgba32> frame = image.Frames.RootFrame; using IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(config); using IndexedImageFrame <Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(1, result.Height); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(0, result.GetPixelRowSpan(0)[0]); }
public void ApplyQuantizationDither <TFrameQuantizer, TPixel>( ref TFrameQuantizer quantizer, ImageFrame <TPixel> source, IndexedImageFrame <TPixel> destination, Rectangle bounds) where TFrameQuantizer : struct, IQuantizer <TPixel> where TPixel : unmanaged, IPixel <TPixel> { var ditherOperation = new QuantizeDitherRowOperation <TFrameQuantizer, TPixel>( ref quantizer, in Unsafe.AsRef(this), source, destination, bounds); ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, in ditherOperation); }
/// <summary> /// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> private void Write1BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration, new QuantizerOptions() { MaxColors = 2 }); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); using IMemoryOwner <byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); Span <byte> colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); ReadOnlySpan <byte> quantizedPixelRow = quantized.GetPixelRowSpan(0); int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { quantizedPixelRow = quantized.GetPixelRowSpan(y); int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; for (int i = 0; i < endIdx; i += 8) { Write1BitPalette(stream, i, i + 8, quantizedPixelRow); } if (quantizedPixelRow.Length % 8 != 0) { int startIdx = quantizedPixelRow.Length - 7; endIdx = quantizedPixelRow.Length; Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); } for (int i = 0; i < rowPadding; i++) { stream.WriteByte(0); } } }
/// <summary> /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> private void Write8BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image, Span <byte> colorPalette) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); for (int y = image.Height - 1; y >= 0; y--) { ReadOnlySpan <byte> pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) { stream.WriteByte(0); } } }
/// <summary> /// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> private void Write4BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration, new QuantizerOptions() { MaxColors = 16 }); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); using IMemoryOwner <byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); Span <byte> colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); ReadOnlySpan <byte> pixelRowSpan = quantized.GetPixelRowSpan(0); int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { pixelRowSpan = quantized.GetPixelRowSpan(y); int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; for (int i = 0; i < endIdx; i += 2) { stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); } if (pixelRowSpan.Length % 2 != 0) { stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0)); } for (int i = 0; i < rowPadding; i++) { stream.WriteByte(0); } } }
/// <summary> /// Calculates the bit depth value. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="options">The options.</param> /// <param name="image">The image.</param> /// <param name="quantizedFrame">The quantized frame.</param> public static byte CalculateBitDepth <TPixel>( PngEncoderOptions options, Image <TPixel> image, IndexedImageFrame <TPixel> quantizedFrame) where TPixel : unmanaged, IPixel <TPixel> { byte bitDepth; if (options.ColorType == PngColorType.Palette) { byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length).Clamp(1, 8); byte bits = Math.Max((byte)options.BitDepth, quantizedBits); // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not // be within the acceptable range. if (bits == 3) { bits = 4; } else if (bits >= 5 && bits <= 7) { bits = 8; } bitDepth = bits; } else { bitDepth = (byte)options.BitDepth; } if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1) { throw new NotSupportedException("Bit depth is not supported or not valid."); } return(bitDepth); }
/// <summary> /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> private void Write8BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image, Span <byte> colorPalette) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); ReadOnlySpan <TPixel> quantizedColors = quantized.Palette.Span; var color = default(Rgba32); // TODO: Use bulk conversion here for better perf int idx = 0; foreach (TPixel quantizedColor in quantizedColors) { quantizedColor.ToRgba32(ref color); colorPalette[idx] = color.B; colorPalette[idx + 1] = color.G; colorPalette[idx + 2] = color.R; // Padding byte, always 0. colorPalette[idx + 3] = 0; idx += 4; } stream.Write(colorPalette); for (int y = image.Height - 1; y >= 0; y--) { ReadOnlySpan <byte> pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) { stream.WriteByte(0); } } }
public static unsafe int RenderAnsi <TColor>(Stream o, IntPtr pixels, uint w, uint h, uint reduceLineCount = 0, int maxLineCount = -1, int maxWidth = -1, bool borderless = false, AnsiColors colors = AnsiColors.TrueColor, IDither?customDither = null, float customDitherScale = 1f ) where TColor : unmanaged, IPixel <TColor> { GetConsoleSize(out var cw, out var ch); if (maxWidth >= 0) { cw = maxWidth; } if (maxLineCount >= 0) { ch = maxLineCount; } cw -= 1; ch -= (int)reduceLineCount; if (cw == 0 || ch == 0) { return(0); } var aw = cw; var ah = ch * 2; var pPixels = (byte *)pixels; var span = new ReadOnlySpan <TColor>(pPixels, checked ((int)(w * h))); using var img = Image.LoadPixelData(span, (int)w, (int)h); img.Mutate(x => x .Resize(aw, ah, LanczosResampler.Lanczos3) //.Crop(aw, ah) ); IndexedImageFrame <TColor>?indexedImg = null; var isTrueColor = colors == AnsiColors.TrueColor; if (!isTrueColor) { switch (colors) { case AnsiColors.Palette16: { var opts = AnsiPalette16.Options; if (customDither != null) { opts.Dither = customDither; opts.DitherScale = customDitherScale; } indexedImg = AnsiPalette16 .CreatePixelSpecificQuantizer <TColor>(Configuration.Default, opts) .QuantizeFrame(img.Frames[0], new Rectangle(0, 0, img.Width, img.Height)); break; } case AnsiColors.Palette256: { var opts = AnsiPalette256.Options; if (customDither != null) { opts.Dither = customDither; opts.DitherScale = customDitherScale; } indexedImg = AnsiPalette256 .CreatePixelSpecificQuantizer <TColor>(Configuration.Default, opts) .QuantizeFrame(img.Frames[0], new Rectangle(0, 0, img.Width, img.Height)); break; } } } void WriteNumberTriplet(byte b) { var ones = b % 10; var tens = b / 10 % 10; var hundreds = b / 100; var anyHundreds = hundreds > 0; if (anyHundreds) { o !.WriteByte((byte)('0' + hundreds)); } if (anyHundreds || tens > 0) { o !.WriteByte((byte)('0' + tens)); } o !.WriteByte((byte)('0' + ones)); } // ╭ void DrawTopLeftCorner() { o.WriteByte(0xE2); o.WriteByte(0x95); o.WriteByte(0xAD); } // ╮ void DrawTopRightCorner() { o.WriteByte(0xE2); o.WriteByte(0x95); o.WriteByte(0xAE); } // ╰ void DrawBottomLeftCorner() { o.WriteByte(0xE2); o.WriteByte(0x95); o.WriteByte(0xB0); } // ╯ void DrawBottomRightCorner() { o.WriteByte(0xE2); o.WriteByte(0x95); o.WriteByte(0xAF); } // ─ x width void DrawHorizontalFrame(int width) { for (var i = 0; i < width; ++i) { o.WriteByte(0xE2); o.WriteByte(0x94); o.WriteByte(0x80); } } // │ void DrawVerticalFrame() { o.WriteByte(0xE2); o.WriteByte(0x94); o.WriteByte(0x82); } if (!borderless) { DrawTopLeftCorner(); DrawHorizontalFrame(aw + 1); DrawTopRightCorner(); o.WriteByte((byte)'\n'); } var lastY = ah & ~1; for (var y = 0; y < lastY; y += 2) { if (!borderless) { DrawVerticalFrame(); } // write 2 lines at a time var haveL = y + 1 < ah; var u = isTrueColor ? img.GetPixelRowSpan(y) : default; var l = haveL && isTrueColor ? img.GetPixelRowSpan(y + 1) : default; var up = !isTrueColor ? indexedImg !.GetPixelRowSpan(y) : default;