Browse Source

Better decoder

Better organisation, handles all color types + maintains image quality.


Former-commit-id: 27d65b60f5e468e9678d0f9dd0314700e658036e
Former-commit-id: 1ed0afaccc0218f60cdc749a83725c9f9693ab4a
Former-commit-id: d20d7fd77362ad6cbb570b46105f2817df99c85c
pull/1/head
James Jackson-South 10 years ago
parent
commit
3fed568e9e
  1. 76
      src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs
  2. 29
      src/ImageProcessorCore/Formats/Png/IColorReader.cs
  3. 95
      src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs
  4. 61
      src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs
  5. 412
      src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs
  6. 2
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
  7. 81
      src/ImageProcessorCore/Formats/Png/TrueColorReader.cs

76
src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs

@ -1,76 +0,0 @@
// <copyright file="GrayscaleReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// Color reader for reading grayscale colors from a png file.
/// </summary>
internal sealed class GrayscaleReader : IColorReader
{
/// <summary>
/// Whether t also read the alpha channel.
/// </summary>
private readonly bool useAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="GrayscaleReader"/> class.
/// </summary>
/// <param name="useAlpha">
/// If set to <c>true</c> the color reader will also read the
/// alpha channel from the scanline.
/// </param>
public GrayscaleReader(bool useAlpha)
{
this.useAlpha = useAlpha;
}
/// <inheritdoc/>
public void ReadScanline<T, TP>(byte[] scanline, T[] pixels, PngHeader header)
where T : IPackedVector<TP>
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++;
}
}
}

29
src/ImageProcessorCore/Formats/Png/IColorReader.cs

@ -1,29 +0,0 @@
// <copyright file="IColorReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// Encapsulates methods for color readers, which are responsible for reading
/// different color formats from a png file.
/// </summary>
public interface IColorReader
{
/// <summary>
/// Reads the specified scanline.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="scanline">The scanline.</param>
/// <param name="pixels">The pixels to read the image row to.</param>
/// <param name="header">
/// The header, which contains information about the png file, like
/// the width of the image and the height.
/// </param>
void ReadScanline<T, TP>(byte[] scanline, T[] pixels, PngHeader header)
where T : IPackedVector<TP>
where TP : struct;
}
}

95
src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs

@ -1,95 +0,0 @@
// <copyright file="PaletteIndexReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// A color reader for reading palette indices from the png file.
/// </summary>
internal sealed class PaletteIndexReader : IColorReader
{
/// <summary>
/// The palette.
/// </summary>
private readonly byte[] palette;
/// <summary>
/// The alpha palette.
/// </summary>
private readonly byte[] paletteAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteIndexReader"/> class.
/// </summary>
/// <param name="palette">The palette as simple byte array. It will contains 3 values for each
/// color, which represents the red-, the green- and the blue channel.</param>
/// <param name="paletteAlpha">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.</param>
public PaletteIndexReader(byte[] palette, byte[] paletteAlpha)
{
this.palette = palette;
this.paletteAlpha = paletteAlpha;
}
/// <inheritdoc/>
public void ReadScanline<T, TP>(byte[] scanline, T[] pixels, PngHeader header)
where T : IPackedVector<TP>
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++;
}
}
}

61
src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs

@ -1,61 +0,0 @@
// <copyright file="PngColorTypeInformation.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
using System;
/// <summary>
/// Contains information that are required when loading a png with a specific color type.
/// </summary>
internal sealed class PngColorTypeInformation
{
/// <summary>
/// Initializes a new instance of the <see cref="PngColorTypeInformation"/> class with
/// the scanline factory, the function to create the color reader and the supported bit depths.
/// </summary>
/// <param name="scanlineFactor">The scanline factor.</param>
/// <param name="supportedBitDepths">The supported bit depths.</param>
/// <param name="scanlineReaderFactory">The factory to create the color reader.</param>
public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func<byte[], byte[], IColorReader> scanlineReaderFactory)
{
this.ChannelsPerColor = scanlineFactor;
this.ScanlineReaderFactory = scanlineReaderFactory;
this.SupportedBitDepths = supportedBitDepths;
}
/// <summary>
/// Gets an array with the bit depths that are supported for the color type
/// where this object is created for.
/// </summary>
/// <value>The supported bit depths that can be used in combination with this color type.</value>
public int[] SupportedBitDepths { get; private set; }
/// <summary>
/// Gets a function that is used the create the color reader for the color type where
/// this object is created for.
/// </summary>
/// <value>The factory function to create the color type.</value>
public Func<byte[], byte[], IColorReader> ScanlineReaderFactory { get; private set; }
/// <summary>
/// Gets a factor that is used when iterating through the scan lines.
/// </summary>
/// <value>The scanline factor.</value>
public int ChannelsPerColor { get; private set; }
/// <summary>
/// Creates the color reader for the color type where this object is create for.
/// </summary>
/// <param name="palette">The palette of the image. Can be null when no palette is used.</param>
/// <param name="paletteAlpha">The alpha palette of the image. Can be null when
/// no palette is used for the image or when the image has no alpha.</param>
/// <returns>The color reader for the image.</returns>
public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha)
{
return this.ScanlineReaderFactory(palette, paletteAlpha);
}
}
}

412
src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs

@ -16,11 +16,10 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
internal class PngDecoderCore internal class PngDecoderCore
{ {
/// <summary> ///// <summary>
/// The dictionary of available color types. ///// The dictionary of available color types.
/// </summary> ///// </summary>
private static readonly Dictionary<int, PngColorTypeInformation> ColorTypes private static readonly Dictionary<int, byte[]> ColorTypes = new Dictionary<int, byte[]>();
= new Dictionary<int, PngColorTypeInformation>();
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
@ -32,29 +31,50 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
private PngHeader header; private PngHeader header;
/// <summary>
/// The number of bytes per pixel.
/// </summary>
private int bytesPerPixel;
/// <summary>
/// The number of bytes per sample
/// </summary>
private int bytesPerSample;
/// <summary>
/// The number of bytes per scanline
/// </summary>
private int bytesPerScanline;
/// <summary>
/// The palette containing color information for indexed pngs
/// </summary>
private byte[] palette;
/// <summary>
/// The palette containing alpha channel color information for indexed pngs
/// </summary>
private byte[] paletteAlpha;
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; }
/// <summary> /// <summary>
/// Initializes static members of the <see cref="PngDecoderCore"/> class. /// Initializes static members of the <see cref="PngDecoderCore"/> class.
/// </summary> /// </summary>
static PngDecoderCore() static PngDecoderCore()
{ {
ColorTypes.Add( ColorTypes.Add((int)PngColorType.Grayscale, new byte[] { 1, 2, 4, 8 });
0,
new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new GrayscaleReader(false)));
ColorTypes.Add( ColorTypes.Add((int)PngColorType.Rgb, new byte[] { 8 });
2,
new PngColorTypeInformation(3, new[] { 8 }, (p, a) => new TrueColorReader(false)));
ColorTypes.Add( ColorTypes.Add((int)PngColorType.Palette, new byte[] { 1, 2, 4, 8 });
3,
new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new PaletteIndexReader(p, a)));
ColorTypes.Add( ColorTypes.Add((int)PngColorType.GrayscaleWithAlpha, new byte[] { 8 });
4,
new PngColorTypeInformation(2, new[] { 8 }, (p, a) => new GrayscaleReader(true)));
ColorTypes.Add(6, ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 });
new PngColorTypeInformation(4, new[] { 8 }, (p, a) => new TrueColorReader(true)));
} }
/// <summary> /// <summary>
@ -80,9 +100,6 @@ namespace ImageProcessorCore.Formats
bool isEndChunkReached = false; bool isEndChunkReached = false;
byte[] palette = null;
byte[] paletteAlpha = null;
using (MemoryStream dataStream = new MemoryStream()) using (MemoryStream dataStream = new MemoryStream())
{ {
PngChunk currentChunk; PngChunk currentChunk;
@ -108,11 +125,12 @@ namespace ImageProcessorCore.Formats
} }
else if (currentChunk.Type == PngChunkTypes.Palette) 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) else if (currentChunk.Type == PngChunkTypes.PaletteAlpha)
{ {
paletteAlpha = currentChunk.Data; this.paletteAlpha = currentChunk.Data;
} }
else if (currentChunk.Type == PngChunkTypes.Text) else if (currentChunk.Type == PngChunkTypes.Text)
{ {
@ -133,54 +151,14 @@ namespace ImageProcessorCore.Formats
T[] pixels = new T[this.header.Width * this.header.Height]; T[] pixels = new T[this.header.Width * this.header.Height];
PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType];
if (colorTypeInformation != null) this.ReadScanlines<T, TP>(dataStream, pixels);
{
IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha);
this.ReadScanlines<T, TP>(dataStream, pixels, colorReader, colorTypeInformation);
}
image.SetPixels(this.header.Width, this.header.Height, pixels); image.SetPixels(this.header.Width, this.header.Height, pixels);
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="left">The left neighbour pixel.</param>
/// <param name="above">The above neighbour pixel.</param>
/// <param name="upperLeft">The upper left neighbour pixel.</param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
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;
}
/// <summary> /// <summary>
/// Reads the data chunk containing physical dimension data. /// Reads the data chunk containing physical dimension data.
/// </summary> /// </summary>
@ -200,14 +178,41 @@ namespace ImageProcessorCore.Formats
image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d;
} }
/// <summary>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
/// <returns>The <see cref="int"/></returns>
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;
}
}
/// <summary> /// <summary>
/// Calculates the scanline length. /// Calculates the scanline length.
/// </summary> /// </summary>
/// <param name="colorTypeInformation">The color type information.</param>
/// <returns>The <see cref="int"/> representing the length.</returns> /// <returns>The <see cref="int"/> representing the length.</returns>
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; int amount = scanlineLength % 8;
if (amount != 0) if (amount != 0)
@ -219,103 +224,227 @@ namespace ImageProcessorCore.Formats
} }
/// <summary> /// <summary>
/// Calculates a scanline step. /// Reads the scanlines within the image.
/// </summary> /// </summary>
/// <param name="colorTypeInformation">The color type information.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <returns>The <see cref="int"/> representing the length of each step.</returns> /// <param name="pixels">
private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation) /// The <see cref="T:float[]"/> containing pixel data.</param>
private void ReadScanlines<T, TP>(MemoryStream dataStream, T[] pixels)
where T : IPackedVector<TP>
where TP : struct
{ {
int scanlineStep = 1; this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength() + 1;
this.bytesPerSample = 1;
if (this.header.BitDepth >= 8) 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<T, TP>(decompressedBytes, pixels);
}
}
} }
/// <summary> /// <summary>
/// Reads the scanlines within the image. /// Decodes the raw pixel data row by row
/// </summary> /// </summary>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <typeparam name="T">The pixel format.</typeparam>
/// <param name="pixels"> /// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// The <see cref="T:float[]"/> containing pixel data.</param> /// <param name="pixelData">The pixel data.</param>
/// <param name="colorReader">The color reader.</param> /// <param name="pixels">The image pixels.</param>
/// <param name="colorTypeInformation">The color type information.</param> private void DecodePixelData<T, TP>(byte[] pixelData, T[] pixels)
private void ReadScanlines<T, TP>(MemoryStream dataStream, T[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation)
where T : IPackedVector<TP> where T : IPackedVector<TP>
where TP : struct where TP : struct
{ {
dataStream.Position = 0; byte[] previousScanline = new byte[this.bytesPerScanline];
int scanlineLength = this.CalculateScanlineLength(colorTypeInformation); for (int y = 0; y < this.header.Height; y++)
int scanlineStep = this.CalculateScanlineStep(colorTypeInformation); {
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]; switch (filterType)
byte[] currentScanline = new byte[scanlineLength]; {
int filter = 0, column = -1; 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<T, TP>(defilteredScanline, y, pixels);
}
}
/// <summary>
/// Processes the defiltered scanline filling the image pixel data
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="defilteredScanline"></param>
/// <param name="row">The current image row.</param>
/// <param name="pixels">The image pixels</param>
private void ProcessDefilteredScanline<T, TP>(byte[] defilteredScanline, int row, T[] pixels)
where T : IPackedVector<TP>
where TP : struct
{
switch (this.PngColorType)
{ {
int readByte; case PngColorType.Grayscale:
while ((readByte = compressedStream.ReadByte()) >= 0)
{ for (int x = 0; x < this.header.Width; x++)
if (column == -1)
{ {
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 intensity = defilteredScanline[offset];
byte b; byte alpha = defilteredScanline[offset + bytesPerSample];
byte c;
if (column >= scanlineStep) T color = default(T);
{ color.PackFromBytes(intensity, intensity, intensity, alpha);
a = currentScanline[column - scanlineStep]; pixels[(row * this.header.Width) + x] = color;
c = lastScanline[column - scanlineStep]; }
}
else
{
a = 0;
c = 0;
}
b = lastScanline[column]; break;
if (filter == 1) case PngColorType.Palette:
{
currentScanline[column] = (byte)(currentScanline[column] + a); byte[] newScanline = defilteredScanline.ToArrayByBitsLength(header.BitDepth);
}
else if (filter == 2) if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{ {
currentScanline[column] = (byte)(currentScanline[column] + b); // 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.
else if (filter == 3) 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) case PngColorType.Rgb:
{
colorReader.ReadScanline<T, TP>(currentScanline, pixels, this.header);
column = -1;
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."); 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."); throw new NotSupportedException("Bit depth is not supported or not valid.");
} }
@ -396,6 +525,8 @@ namespace ImageProcessorCore.Formats
// TODO: Support interlacing // TODO: Support interlacing
throw new NotSupportedException("Interlacing is not supported."); throw new NotSupportedException("Interlacing is not supported.");
} }
this.PngColorType = (PngColorType)this.header.ColorType;
} }
/// <summary> /// <summary>
@ -525,20 +656,5 @@ namespace ImageProcessorCore.Formats
return numBytes; return numBytes;
} }
/// <summary>
/// Swaps two references.
/// </summary>
/// <typeparam name="TRef">The type of the references to swap.</typeparam>
/// <param name="lhs">The first reference.</param>
/// <param name="rhs">The second reference.</param>
private void Swap<TRef>(ref TRef lhs, ref TRef rhs)
where TRef : class
{
TRef tmp = lhs;
lhs = rhs;
rhs = tmp;
}
} }
} }

2
src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

@ -126,7 +126,7 @@ namespace ImageProcessorCore.Formats
this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue;
// Set correct color type if the color count is 256 or less. // Set correct color type if the color count is 256 or less.
if (Quality <= 256) if (this.Quality <= 256)
{ {
this.PngColorType = PngColorType.Palette; this.PngColorType = PngColorType.Palette;
} }

81
src/ImageProcessorCore/Formats/Png/TrueColorReader.cs

@ -1,81 +0,0 @@
// <copyright file="TrueColorReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// 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.
/// </summary>
internal sealed class TrueColorReader : IColorReader
{
/// <summary>
/// Whether t also read the alpha channel.
/// </summary>
private readonly bool useAlpha;
/// <summary>
/// The current row.
/// </summary>
private int row;
/// <summary>
/// Initializes a new instance of the <see cref="TrueColorReader"/> class.
/// </summary>
/// <param name="useAlpha">if set to <c>true</c> the color reader will also read the
/// alpha channel from the scanline.</param>
public TrueColorReader(bool useAlpha)
{
this.useAlpha = useAlpha;
}
/// <inheritdoc/>
public void ReadScanline<T, TP>(byte[] scanline, T[] pixels, PngHeader header)
where T : IPackedVector<TP>
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++;
}
}
}
Loading…
Cancel
Save