diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs deleted file mode 100644 index cc813a6eb..000000000 --- a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Color reader for reading grayscale colors from a png file. - /// - internal sealed class GrayscaleReader : IColorReader - { - /// - /// Whether t also read the alpha channel. - /// - private readonly bool useAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// - /// If set to true the color reader will also read the - /// alpha channel from the scanline. - /// - public GrayscaleReader(bool useAlpha) - { - this.useAlpha = useAlpha; - } - - /// - public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector - where TP : struct - { - int offset; - - byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - - // Stored in r-> g-> b-> a order. - if (this.useAlpha) - { - for (int x = 0; x < header.Width / 2; x++) - { - offset = (this.row * header.Width) + x; - - byte rgb = newScanline[x * 2]; - byte a = newScanline[(x * 2) + 1]; - - T color = default(T); - color.PackFromBytes(rgb, rgb, rgb, a); - pixels[offset] = color; - } - } - else - { - for (int x = 0; x < header.Width; x++) - { - offset = (this.row * header.Width) + x; - byte rgb = newScanline[x]; - - T color = default(T); - color.PackFromBytes(rgb, rgb, rgb, 255); - - pixels[offset] = color; - } - } - - this.row++; - } - } -} diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore/Formats/Png/IColorReader.cs deleted file mode 100644 index 88ce0d4d2..000000000 --- a/src/ImageProcessorCore/Formats/Png/IColorReader.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates methods for color readers, which are responsible for reading - /// different color formats from a png file. - /// - public interface IColorReader - { - /// - /// Reads the specified scanline. - /// - /// The pixel format. - /// The packed format. long, float. - /// The scanline. - /// The pixels to read the image row to. - /// - /// The header, which contains information about the png file, like - /// the width of the image and the height. - /// - void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector - where TP : struct; - } -} diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs deleted file mode 100644 index 52d88e022..000000000 --- a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs +++ /dev/null @@ -1,95 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// A color reader for reading palette indices from the png file. - /// - internal sealed class PaletteIndexReader : IColorReader - { - /// - /// The palette. - /// - private readonly byte[] palette; - - /// - /// The alpha palette. - /// - private readonly byte[] paletteAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// The palette as simple byte array. It will contains 3 values for each - /// color, which represents the red-, the green- and the blue channel. - /// The alpha palette. Can be null, if the image does not have an - /// alpha channel and can contain less entries than the number of colors in the palette. - public PaletteIndexReader(byte[] palette, byte[] paletteAlpha) - { - this.palette = palette; - this.paletteAlpha = paletteAlpha; - } - - /// - public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector - where TP : struct - { - byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - int offset, index; - - if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) - { - // If the alpha palette is not null and does one or - // more entries, this means, that the image contains and alpha - // channel and we should try to read it. - for (int i = 0; i < header.Width; i++) - { - index = newScanline[i]; - - offset = (this.row * header.Width) + i; - int pixelOffset = index * 3; - - byte r = this.palette[pixelOffset]; - byte g = this.palette[pixelOffset + 1]; - byte b = this.palette[pixelOffset + 2]; - byte a = this.paletteAlpha.Length > index - ? this.paletteAlpha[index] - : (byte)255; - - T color = default(T); - color.PackFromBytes(r, g, b, a); - pixels[offset] = color; - } - } - else - { - for (int i = 0; i < header.Width; i++) - { - index = newScanline[i]; - - offset = (this.row * header.Width) + i; - int pixelOffset = index * 3; - - byte r = this.palette[pixelOffset]; - byte g = this.palette[pixelOffset + 1]; - byte b = this.palette[pixelOffset + 2]; - - T color = default(T); - color.PackFromBytes(r, g, b, 255); - pixels[offset] = color; - } - } - - this.row++; - } - } -} diff --git a/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs deleted file mode 100644 index 9909cf47c..000000000 --- a/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - - /// - /// Contains information that are required when loading a png with a specific color type. - /// - internal sealed class PngColorTypeInformation - { - /// - /// Initializes a new instance of the class with - /// the scanline factory, the function to create the color reader and the supported bit depths. - /// - /// The scanline factor. - /// The supported bit depths. - /// The factory to create the color reader. - public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func scanlineReaderFactory) - { - this.ChannelsPerColor = scanlineFactor; - this.ScanlineReaderFactory = scanlineReaderFactory; - this.SupportedBitDepths = supportedBitDepths; - } - - /// - /// Gets an array with the bit depths that are supported for the color type - /// where this object is created for. - /// - /// The supported bit depths that can be used in combination with this color type. - public int[] SupportedBitDepths { get; private set; } - - /// - /// Gets a function that is used the create the color reader for the color type where - /// this object is created for. - /// - /// The factory function to create the color type. - public Func ScanlineReaderFactory { get; private set; } - - /// - /// Gets a factor that is used when iterating through the scan lines. - /// - /// The scanline factor. - public int ChannelsPerColor { get; private set; } - - /// - /// Creates the color reader for the color type where this object is create for. - /// - /// The palette of the image. Can be null when no palette is used. - /// The alpha palette of the image. Can be null when - /// no palette is used for the image or when the image has no alpha. - /// The color reader for the image. - public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha) - { - return this.ScanlineReaderFactory(palette, paletteAlpha); - } - } -} diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs index f97e085d5..2e8f81c19 100644 --- a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs @@ -16,11 +16,10 @@ namespace ImageProcessorCore.Formats /// internal class PngDecoderCore { - /// - /// The dictionary of available color types. - /// - private static readonly Dictionary ColorTypes - = new Dictionary(); + ///// + ///// The dictionary of available color types. + ///// + private static readonly Dictionary ColorTypes = new Dictionary(); /// /// The stream to decode from. @@ -32,29 +31,50 @@ namespace ImageProcessorCore.Formats /// private PngHeader header; + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// The number of bytes per sample + /// + private int bytesPerSample; + + /// + /// The number of bytes per scanline + /// + private int bytesPerScanline; + + /// + /// The palette containing color information for indexed pngs + /// + private byte[] palette; + + /// + /// The palette containing alpha channel color information for indexed pngs + /// + private byte[] paletteAlpha; + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } + /// /// Initializes static members of the class. /// static PngDecoderCore() { - ColorTypes.Add( - 0, - new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new GrayscaleReader(false))); + ColorTypes.Add((int)PngColorType.Grayscale, new byte[] { 1, 2, 4, 8 }); - ColorTypes.Add( - 2, - new PngColorTypeInformation(3, new[] { 8 }, (p, a) => new TrueColorReader(false))); + ColorTypes.Add((int)PngColorType.Rgb, new byte[] { 8 }); - ColorTypes.Add( - 3, - new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new PaletteIndexReader(p, a))); + ColorTypes.Add((int)PngColorType.Palette, new byte[] { 1, 2, 4, 8 }); - ColorTypes.Add( - 4, - new PngColorTypeInformation(2, new[] { 8 }, (p, a) => new GrayscaleReader(true))); + ColorTypes.Add((int)PngColorType.GrayscaleWithAlpha, new byte[] { 8 }); - ColorTypes.Add(6, - new PngColorTypeInformation(4, new[] { 8 }, (p, a) => new TrueColorReader(true))); + ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); } /// @@ -80,9 +100,6 @@ namespace ImageProcessorCore.Formats bool isEndChunkReached = false; - byte[] palette = null; - byte[] paletteAlpha = null; - using (MemoryStream dataStream = new MemoryStream()) { PngChunk currentChunk; @@ -108,11 +125,12 @@ namespace ImageProcessorCore.Formats } else if (currentChunk.Type == PngChunkTypes.Palette) { - palette = currentChunk.Data; + this.palette = currentChunk.Data; + image.Quality = this.palette.Length / 3; } else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) { - paletteAlpha = currentChunk.Data; + this.paletteAlpha = currentChunk.Data; } else if (currentChunk.Type == PngChunkTypes.Text) { @@ -133,54 +151,14 @@ namespace ImageProcessorCore.Formats T[] pixels = new T[this.header.Width * this.header.Height]; - PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType]; - if (colorTypeInformation != null) - { - IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha); + this.ReadScanlines(dataStream, pixels); - this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation); - } image.SetPixels(this.header.Width, this.header.Height, pixels); } } - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbour pixel. - /// The above neighbour pixel. - /// The upper left neighbour pixel. - /// - /// The . - /// - private static byte PaethPredicator(byte left, byte above, byte upperLeft) - { - byte predicator; - - int p = left + above - upperLeft; - int pa = Math.Abs(p - left); - int pb = Math.Abs(p - above); - int pc = Math.Abs(p - upperLeft); - - if (pa <= pb && pa <= pc) - { - predicator = left; - } - else if (pb <= pc) - { - predicator = above; - } - else - { - predicator = upperLeft; - } - - return predicator; - } - /// /// Reads the data chunk containing physical dimension data. /// @@ -200,14 +178,41 @@ namespace ImageProcessorCore.Formats image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; } + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// The + private int CalculateBytesPerPixel() + { + switch (this.PngColorType) + { + case PngColorType.Grayscale: + return 1; + + case PngColorType.GrayscaleWithAlpha: + return 2; + + case PngColorType.Palette: + return 1; + + case PngColorType.Rgb: + return 3; + + // PngColorType.RgbWithAlpha + // TODO: Maybe figure out a way to detect if there are any transparent + // pixels and encode RGB if none. + default: + return 4; + } + } + /// /// Calculates the scanline length. /// - /// The color type information. /// The representing the length. - private int CalculateScanlineLength(PngColorTypeInformation colorTypeInformation) + private int CalculateScanlineLength() { - int scanlineLength = this.header.Width * this.header.BitDepth * colorTypeInformation.ChannelsPerColor; + int scanlineLength = this.header.Width * this.header.BitDepth * this.bytesPerPixel; int amount = scanlineLength % 8; if (amount != 0) @@ -219,103 +224,227 @@ namespace ImageProcessorCore.Formats } /// - /// Calculates a scanline step. + /// Reads the scanlines within the image. /// - /// The color type information. - /// The representing the length of each step. - private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation) + /// The containing data. + /// + /// The containing pixel data. + private void ReadScanlines(MemoryStream dataStream, T[] pixels) + where T : IPackedVector + where TP : struct { - int scanlineStep = 1; - + this.bytesPerPixel = this.CalculateBytesPerPixel(); + this.bytesPerScanline = this.CalculateScanlineLength() + 1; + this.bytesPerSample = 1; if (this.header.BitDepth >= 8) { - scanlineStep = (colorTypeInformation.ChannelsPerColor * this.header.BitDepth) / 8; + this.bytesPerSample = (this.header.BitDepth) / 8; } - return scanlineStep; + dataStream.Position = 0; + using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) + { + using (MemoryStream decompressedStream = new MemoryStream()) + { + compressedStream.CopyTo(decompressedStream); + decompressedStream.Flush(); + + byte[] decompressedBytes = decompressedStream.ToArray(); + DecodePixelData(decompressedBytes, pixels); + } + } } /// - /// Reads the scanlines within the image. + /// Decodes the raw pixel data row by row /// - /// The containing data. - /// - /// The containing pixel data. - /// The color reader. - /// The color type information. - private void ReadScanlines(MemoryStream dataStream, T[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) + /// The pixel format. + /// The packed format. long, float. + /// The pixel data. + /// The image pixels. + private void DecodePixelData(byte[] pixelData, T[] pixels) where T : IPackedVector where TP : struct { - dataStream.Position = 0; + byte[] previousScanline = new byte[this.bytesPerScanline]; - int scanlineLength = this.CalculateScanlineLength(colorTypeInformation); - int scanlineStep = this.CalculateScanlineStep(colorTypeInformation); + for (int y = 0; y < this.header.Height; y++) + { + byte[] scanline = new byte[this.bytesPerScanline]; + Array.Copy(pixelData, y * this.bytesPerScanline, scanline, 0, this.bytesPerScanline); + FilterType filterType = (FilterType)scanline[0]; + byte[] defilteredScanline; - byte[] lastScanline = new byte[scanlineLength]; - byte[] currentScanline = new byte[scanlineLength]; - int filter = 0, column = -1; + switch (filterType) + { + case FilterType.None: - using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) + defilteredScanline = NoneFilter.Decode(scanline); + + break; + + case FilterType.Sub: + + defilteredScanline = SubFilter.Decode(scanline, bytesPerPixel); + + break; + + case FilterType.Up: + + defilteredScanline = UpFilter.Decode(scanline, previousScanline); + + break; + + case FilterType.Average: + + defilteredScanline = AverageFilter.Decode(scanline, previousScanline, bytesPerPixel); + + break; + + case FilterType.Paeth: + + defilteredScanline = PaethFilter.Decode(scanline, previousScanline, bytesPerPixel); + + break; + + default: + throw new ImageFormatException("Unknown filter type."); + } + + previousScanline = defilteredScanline; + ProcessDefilteredScanline(defilteredScanline, y, pixels); + } + } + + /// + /// Processes the defiltered scanline filling the image pixel data + /// + /// The pixel format. + /// The packed format. long, float. + /// + /// The current image row. + /// The image pixels + private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, T[] pixels) + where T : IPackedVector + where TP : struct + { + switch (this.PngColorType) { - int readByte; - while ((readByte = compressedStream.ReadByte()) >= 0) - { - if (column == -1) + case PngColorType.Grayscale: + + for (int x = 0; x < this.header.Width; x++) { - filter = readByte; + int offset = 1 + (x * bytesPerPixel); + + byte intensity = defilteredScanline[offset]; - column++; + T color = default(T); + color.PackFromBytes(intensity, intensity, intensity, 255); + pixels[(row * this.header.Width) + x] = color; } - else + + break; + + case PngColorType.GrayscaleWithAlpha: + + for (int x = 0; x < this.header.Width; x++) { - currentScanline[column] = (byte)readByte; + int offset = 1 + (x * bytesPerPixel); - byte a; - byte b; - byte c; + byte intensity = defilteredScanline[offset]; + byte alpha = defilteredScanline[offset + bytesPerSample]; - if (column >= scanlineStep) - { - a = currentScanline[column - scanlineStep]; - c = lastScanline[column - scanlineStep]; - } - else - { - a = 0; - c = 0; - } + T color = default(T); + color.PackFromBytes(intensity, intensity, intensity, alpha); + pixels[(row * this.header.Width) + x] = color; + } - b = lastScanline[column]; + break; - if (filter == 1) - { - currentScanline[column] = (byte)(currentScanline[column] + a); - } - else if (filter == 2) - { - currentScanline[column] = (byte)(currentScanline[column] + b); - } - else if (filter == 3) + case PngColorType.Palette: + + byte[] newScanline = defilteredScanline.ToArrayByBitsLength(header.BitDepth); + + if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) + { + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + for (int i = 0; i < header.Width; i++) { - currentScanline[column] = (byte)(currentScanline[column] + (byte)((a + b) / 2)); + int index = newScanline[i]; + int offset = (row * header.Width) + i; + int pixelOffset = index * 3; + + byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; + T color = default(T); + if (a > 0) + { + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + color.PackFromBytes(r, g, b, a); + } + + pixels[offset] = color; } - else if (filter == 4) + } + else + { + for (int i = 0; i < header.Width; i++) { - currentScanline[column] = (byte)(currentScanline[column] + PaethPredicator(a, b, c)); + int index = newScanline[i]; + int offset = (row * header.Width) + i; + int pixelOffset = index * 3; + + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + + T color = default(T); + color.PackFromBytes(r, g, b, 255); + pixels[offset] = color; } + } - column++; + break; - if (column == scanlineLength) - { - colorReader.ReadScanline(currentScanline, pixels, this.header); - column = -1; + case PngColorType.Rgb: - this.Swap(ref currentScanline, ref lastScanline); - } + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * bytesPerPixel); + + byte r = defilteredScanline[offset]; + byte g = defilteredScanline[offset + bytesPerSample]; + byte b = defilteredScanline[offset + 2 * bytesPerSample]; + + T color = default(T); + color.PackFromBytes(r, g, b, 255); + pixels[(row * this.header.Width) + x] = color; } - } + + break; + + case PngColorType.RgbWithAlpha: + + for (int x = 0; x < this.header.Width; x++) + { + int offset = 1 + (x * bytesPerPixel); + + byte r = defilteredScanline[offset]; + byte g = defilteredScanline[offset + bytesPerSample]; + byte b = defilteredScanline[offset + 2 * bytesPerSample]; + byte a = defilteredScanline[offset + 3 * bytesPerSample]; + + T color = default(T); + color.PackFromBytes(r, g, b, a); + pixels[(row * this.header.Width) + x] = color; + } + + break; + + default: + break; } } @@ -381,7 +510,7 @@ namespace ImageProcessorCore.Formats throw new NotSupportedException("Color type is not supported or not valid."); } - if (!ColorTypes[this.header.ColorType].SupportedBitDepths.Contains(this.header.BitDepth)) + if (!ColorTypes[this.header.ColorType].Contains(this.header.BitDepth)) { throw new NotSupportedException("Bit depth is not supported or not valid."); } @@ -396,6 +525,8 @@ namespace ImageProcessorCore.Formats // TODO: Support interlacing throw new NotSupportedException("Interlacing is not supported."); } + + this.PngColorType = (PngColorType)this.header.ColorType; } /// @@ -525,20 +656,5 @@ namespace ImageProcessorCore.Formats return numBytes; } - - /// - /// Swaps two references. - /// - /// The type of the references to swap. - /// The first reference. - /// The second reference. - private void Swap(ref TRef lhs, ref TRef rhs) - where TRef : class - { - TRef tmp = lhs; - - lhs = rhs; - rhs = tmp; - } } } diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index 2381ae44c..97ab62dff 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -126,7 +126,7 @@ namespace ImageProcessorCore.Formats this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; // Set correct color type if the color count is 256 or less. - if (Quality <= 256) + if (this.Quality <= 256) { this.PngColorType = PngColorType.Palette; } diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs deleted file mode 100644 index 86ac7db38..000000000 --- a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Color reader for reading true colors from a png file. Only colors - /// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment. - /// - internal sealed class TrueColorReader : IColorReader - { - /// - /// Whether t also read the alpha channel. - /// - private readonly bool useAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true the color reader will also read the - /// alpha channel from the scanline. - public TrueColorReader(bool useAlpha) - { - this.useAlpha = useAlpha; - } - - /// - public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector - where TP : struct - { - int offset; - - byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - - if (this.useAlpha) - { - for (int x = 0; x < newScanline.Length; x += 4) - { - offset = (this.row * header.Width) + (x >> 2); - - // We want to convert to premultiplied alpha here. - byte r = newScanline[x]; - byte g = newScanline[x + 1]; - byte b = newScanline[x + 2]; - byte a = newScanline[x + 3]; - - T color = default(T); - color.PackFromBytes(r, g, b, a); - - pixels[offset] = color; - } - } - else - { - for (int x = 0; x < newScanline.Length / 3; x++) - { - offset = (this.row * header.Width) + x; - int pixelOffset = x * 3; - - byte r = newScanline[pixelOffset]; - byte g = newScanline[pixelOffset + 1]; - byte b = newScanline[pixelOffset + 2]; - - T color = default(T); - color.PackFromBytes(r, g, b, 255); - pixels[offset] = color; - } - } - - this.row++; - } - } -}