diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 48d611f58b..a3ffeb296d 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -3,7 +3,9 @@
using System;
using System.Buffers.Binary;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
@@ -23,6 +25,18 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal sealed class PngEncoderCore : IDisposable
{
+ ///
+ /// The dictionary of available color types.
+ ///
+ private static readonly Dictionary ColorTypes = new Dictionary()
+ {
+ [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 },
+ [PngColorType.Rgb] = new byte[] { 8, 16 },
+ [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
+ [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 },
+ [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
+ };
+
///
/// Used the manage memory allocations.
///
@@ -198,19 +212,27 @@ namespace SixLabors.ImageSharp.Formats.Png
this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth;
this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
+ // Ensure we are not allowing impossible combinations.
+ if (!ColorTypes.ContainsKey(this.pngColorType.Value))
+ {
+ throw new NotSupportedException("Color type is not supported or not valid.");
+ }
+
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
if (this.pngColorType == PngColorType.Palette)
{
- byte bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
+ byte bits = (byte)this.pngBitDepth;
+ if (!ColorTypes[this.pngColorType.Value].Contains(bits))
+ {
+ throw new NotSupportedException("Bit depth is not supported or not valid.");
+ }
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (this.quantizer == null)
{
- bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
- int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
- this.quantizer = new WuQuantizer(colorSize);
+ this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
}
// Create quantized frame returning the palette and set the bit depth.
@@ -234,8 +256,11 @@ namespace SixLabors.ImageSharp.Formats.Png
}
else
{
- // TODO: Get the correct bit depth for grayscale images.
- this.bitDepth = (byte)(this.use16Bit ? 16 : 8);
+ this.bitDepth = (byte)this.pngBitDepth;
+ if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth))
+ {
+ throw new NotSupportedException("Bit depth is not supported or not valid.");
+ }
}
this.bytesPerPixel = this.CalculateBytesPerPixel();
@@ -295,9 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.pngColorType.Equals(PngColorType.Grayscale))
{
- // TODO: Realistically we should support 1, 2, 4, 8, and 16 bit grayscale images.
- // we currently do the other types via palette. Maybe RC as I don't understand how the data is packed yet
- // for 1, 2, and 4 bit grayscale images.
+ // TODO: Research and add support for grayscale plus tRNS
if (this.use16Bit)
{
// 16 bit grayscale
@@ -311,12 +334,32 @@ namespace SixLabors.ImageSharp.Formats.Png
}
else
{
- // 8 bit grayscale
- Rgb24 rgb = default;
- for (int x = 0; x < rowSpan.Length; x++)
+ if (this.bitDepth == 8)
{
- rowSpan[x].ToRgb24(ref rgb);
- rawScanlineSpan[x] = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));
+ // 8 bit grayscale
+ Rgb24 rgb = default;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ rowSpan[x].ToRgb24(ref rgb);
+ rawScanlineSpan[x] = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));
+ }
+ }
+ else
+ {
+ // 1, 2, and 4 bit grayscale
+ using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer(rowSpan.Length, AllocationOptions.Clean))
+ {
+ int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(this.bitDepth) - 1);
+ Span tempSpan = temp.GetSpan();
+ Rgb24 rgb = default;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ rowSpan[x].ToRgb24(ref rgb);
+ float luminance = ((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)) / scaleFactor;
+ tempSpan[x] = (byte)luminance;
+ this.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth);
+ }
+ }
}
}
}