Handle unpacking components for 16 bits per component images and use Span in RemoveStridePadding

This commit is contained in:
BobLd 2024-08-24 20:24:19 +01:00
parent 5c168f9cd0
commit ad785328e1
2 changed files with 79 additions and 42 deletions

View File

@ -40,6 +40,25 @@
}
}
[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()
{

View File

@ -1,56 +1,73 @@
namespace UglyToad.PdfPig.Images
{
using Content;
namespace UglyToad.PdfPig.Images
{
using Content;
using Graphics.Colors;
using System;
using System.Linq;
using System;
/// <summary>
/// Utility for working with the bytes in <see cref="IPdfImage"/>s and converting according to their <see cref="ColorSpaceDetails"/>.s
/// </summary>
public static class ColorSpaceDetailsByteConverter
{
/// <summary>
/// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytesAsMemory"/>
/// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't
/// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the
/// real pixel data into the real pixel data.
/// </summary>
public static ReadOnlySpan<byte> Convert(ColorSpaceDetails details, ReadOnlySpan<byte> decoded, int bitsPerComponent, int imageWidth, int imageHeight)
{
if (decoded.IsEmpty)
{
return [];
}
if (details is null)
{
return decoded;
/// <summary>
/// Utility for working with the bytes in <see cref="IPdfImage"/>s and converting according to their <see cref="ColorSpaceDetails"/>.s
/// </summary>
public static class ColorSpaceDetailsByteConverter
{
/// <summary>
/// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytesAsMemory"/>
/// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't
/// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the
/// real pixel data into the real pixel data.
/// </summary>
public static ReadOnlySpan<byte> Convert(ColorSpaceDetails details, ReadOnlySpan<byte> 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<byte>
Span<byte> 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<byte> input, int bitsPerComponent)
private static Span<byte> UnpackComponents(Span<byte> 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<byte> unpacked = new byte[input.Length * (int)Math.Ceiling((end + 1) / (double)bitsPerComponent)];
int right = (int)Math.Pow(2, bitsPerComponent) - 1;
@ -65,19 +82,20 @@
}
return unpacked;
}
private static byte[] RemoveStridePadding(byte[] input, int strideWidth, int imageWidth, int imageHeight, int multiplier)
}
private static Span<byte> RemoveStridePadding(Span<byte> input, int strideWidth, int imageWidth, int imageHeight, int multiplier)
{
var result = new byte[imageWidth * imageHeight * multiplier];
Span<byte> 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;
}
}
}
}
}
}