diff --git a/src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs b/src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs
index fa2b2482b..e8a4fa694 100644
--- a/src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs
+++ b/src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs
@@ -40,6 +40,25 @@ public void IndexedDeviceNColorSpaceImages()
}
}
+ [Fact]
+ public void BitsPerComponents16()
+ {
+ var path = IntegrationHelpers.GetDocumentPath("MOZILLA-3136-0.pdf");
+
+ using (var document = PdfDocument.Open(path))
+ {
+ var page1 = document.GetPage(3);
+ var images1 = page1.GetImages().ToArray();
+
+ var image9 = images1[9];
+
+ Assert.Equal(16 , image9.BitsPerComponent);
+
+ Assert.True(image9.TryGetPng(out byte[] bytes_3_9));
+ File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-3136-0_3_9_16bits.png"), bytes_3_9);
+ }
+ }
+
[Fact]
public void DeviceNColorSpaceImages()
{
diff --git a/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs b/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs
index 55022b9a3..277bccb31 100644
--- a/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs
+++ b/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs
@@ -1,56 +1,73 @@
-namespace UglyToad.PdfPig.Images
-{
- using Content;
+namespace UglyToad.PdfPig.Images
+{
+ using Content;
using Graphics.Colors;
- using System;
- using System.Linq;
-
- ///
- /// Utility for working with the bytes in s and converting according to their .s
- ///
- public static class ColorSpaceDetailsByteConverter
- {
- ///
- /// Converts the output bytes (if available) of
- /// to actual pixel values using the . For most images this doesn't
- /// change the data but for it will convert the bytes which are indexes into the
- /// real pixel data into the real pixel data.
- ///
- public static ReadOnlySpan Convert(ColorSpaceDetails details, ReadOnlySpan decoded, int bitsPerComponent, int imageWidth, int imageHeight)
- {
- if (decoded.IsEmpty)
- {
- return [];
- }
-
- if (details is null)
- {
- return decoded;
+ using System;
+
+ ///
+ /// Utility for working with the bytes in s and converting according to their .s
+ ///
+ public static class ColorSpaceDetailsByteConverter
+ {
+ ///
+ /// Converts the output bytes (if available) of
+ /// to actual pixel values using the . For most images this doesn't
+ /// change the data but for it will convert the bytes which are indexes into the
+ /// real pixel data into the real pixel data.
+ ///
+ public static ReadOnlySpan Convert(ColorSpaceDetails details, ReadOnlySpan decoded, int bitsPerComponent, int imageWidth, int imageHeight)
+ {
+ if (decoded.IsEmpty)
+ {
+ return [];
+ }
+
+ if (details is null)
+ {
+ return decoded;
}
+ // TODO - We should aim at removing this alloc.
+ // The decoded input variable needs to become a Span
+ Span data = decoded.ToArray();
+
if (bitsPerComponent != 8)
{
// Unpack components such that they occupy one byte each
- decoded = UnpackComponents(decoded, bitsPerComponent);
+ data = UnpackComponents(data, bitsPerComponent);
}
// Remove padding bytes when the stride width differs from the image width
var bytesPerPixel = details.NumberOfColorComponents;
- var strideWidth = decoded.Length / imageHeight / bytesPerPixel;
+ var strideWidth = data.Length / imageHeight / bytesPerPixel;
if (strideWidth != imageWidth)
{
- decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight, bytesPerPixel);
+ data = RemoveStridePadding(data, strideWidth, imageWidth, imageHeight, bytesPerPixel);
}
- decoded = details.Transform(decoded);
-
- return decoded;
+ return details.Transform(data);
}
- private static byte[] UnpackComponents(ReadOnlySpan input, int bitsPerComponent)
+ private static Span UnpackComponents(Span input, int bitsPerComponent)
{
+ if (bitsPerComponent == 16) // Example with MOZILLA-3136-0.pdf (page 3)
+ {
+ int size = input.Length / 2;
+ var unpacked16 = input.Slice(0, size); // In place
+
+ for (int b = 0; b < size; ++b)
+ {
+ int i = 2 * b;
+ // Convert to UInt16 and divide by 256
+ unpacked16[b] = (byte)((ushort)(input[i + 1] | input[i] << 8) / 256);
+ }
+
+ return unpacked16;
+ }
+
int end = 8 - bitsPerComponent;
- var unpacked = new byte[input.Length * (int)Math.Ceiling((end + 1) / (double)bitsPerComponent)];
+
+ Span unpacked = new byte[input.Length * (int)Math.Ceiling((end + 1) / (double)bitsPerComponent)];
int right = (int)Math.Pow(2, bitsPerComponent) - 1;
@@ -65,19 +82,20 @@ private static byte[] UnpackComponents(ReadOnlySpan input, int bitsPerComp
}
return unpacked;
- }
-
- private static byte[] RemoveStridePadding(byte[] input, int strideWidth, int imageWidth, int imageHeight, int multiplier)
+ }
+
+
+ private static Span RemoveStridePadding(Span input, int strideWidth, int imageWidth, int imageHeight, int multiplier)
{
- var result = new byte[imageWidth * imageHeight * multiplier];
+ Span result = new byte[imageWidth * imageHeight * multiplier];
for (int y = 0; y < imageHeight; y++)
{
int sourceIndex = y * strideWidth;
int targetIndex = y * imageWidth;
- Array.Copy(input, sourceIndex, result, targetIndex, imageWidth);
+ input.Slice(sourceIndex, imageWidth).CopyTo(result.Slice(targetIndex, imageWidth));
}
return result;
- }
- }
-}
+ }
+ }
+}