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; - } - } -} + } + } +}