Improve Jpeg2000Helper to support J2K codec and add test
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled

This commit is contained in:
BobLd 2025-03-09 13:55:36 +00:00
parent a4a0fe220a
commit 204f488ebf
4 changed files with 42 additions and 20 deletions

View File

@ -2,12 +2,13 @@
{
using PdfPig.Images;
using System;
using UglyToad.PdfPig.Tests.Integration;
public class Jpeg2000HelperTests
{
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files", "Jpx")));
public static IEnumerable<object[]> GetAllDocuments
public static IEnumerable<object[]> GetAllJp2Files
{
get
{
@ -15,24 +16,40 @@
}
}
[Theory]
[MemberData(nameof(GetAllJp2Files))]
public void GetJp2BitsPerComponent_ReturnsCorrectBitsPerComponent_WhenValidInput(string path)
{
byte[] image = File.ReadAllBytes(Path.Combine(DocumentFolder.Value, path));
Assert.Equal(8, Jpeg2000Helper.GetBitsPerComponent(image));
}
[Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenInputIsTooShort()
{
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetJp2BitsPerComponent(new byte[11]));
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[11]));
}
[Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenSignatureBoxIsInvalid()
{
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetJp2BitsPerComponent(new byte[12]));
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[12]));
}
[Theory]
[MemberData(nameof(GetAllDocuments))]
public void GetJp2BitsPerComponent_ReturnsCorrectBitsPerComponent_WhenValidInput(string path)
[Fact]
public void GetJp2BitsPerComponentJ2K()
{
byte[] image = File.ReadAllBytes(Path.Combine(DocumentFolder.Value, path));
Assert.Equal(8, Jpeg2000Helper.GetJp2BitsPerComponent(image));
string path = IntegrationHelpers.GetSpecificTestDocumentPath("GHOSTSCRIPT-688999-2.pdf");
using (var document = PdfDocument.Open(path))
{
var page1 = document.GetPage(1);
var jpxImage = page1.GetImages().Single();
var bpc = Jpeg2000Helper.GetBitsPerComponent(jpxImage.RawBytes);
Assert.Equal(8, bpc);
}
}
}
}

View File

@ -8,7 +8,7 @@
/// <summary>
/// Get bits per component values for Jp2 (Jpx) encoded images (first component).
/// </summary>
public static byte GetJp2BitsPerComponent(ReadOnlySpan<byte> jp2Bytes)
public static byte GetBitsPerComponent(ReadOnlySpan<byte> jp2Bytes)
{
// Ensure the input has at least 12 bytes for the signature box
if (jp2Bytes.Length < 12)
@ -18,16 +18,21 @@
// Verify the JP2 signature box
uint length = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(0, 4));
uint type = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(4, 4));
uint magic = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(8, 4));
if (length != 0x0000000C || type != 0x6A502020 || magic != 0x0D0A870A)
if (length == 0xFF4FFF51)
{
throw new InvalidOperationException("Invalid JP2 signature box.");
// J2K format detected (SOC marker) (See GHOSTSCRIPT-688999-2.pdf)
return ParseCodestream(jp2Bytes);
}
// Proceed to parse JP2 boxes
return ParseBoxes(jp2Bytes.Slice(12));
uint type = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(4, 4));
uint magic = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(8, 4));
if (length == 0x0000000C && type == 0x6A502020 && magic == 0x0D0A870A)
{
// JP2 format detected
return ParseBoxes(jp2Bytes.Slice(12));
}
throw new InvalidOperationException("Invalid JP2 or J2K signature.");
}
private static byte ParseBoxes(ReadOnlySpan<byte> jp2Bytes)
@ -37,7 +42,7 @@
{
if (offset + 8 > jp2Bytes.Length)
{
throw new InvalidOperationException("Invalid JP2 box structure.");
throw new InvalidOperationException("Invalid JP2 or J2K box structure.");
}
// Read box length and type
@ -55,7 +60,7 @@
offset += (int)(boxLength > 0 ? boxLength : 8); // Box length of 0 means the rest of the file
}
throw new InvalidOperationException("Codestream box not found in JP2 file.");
throw new InvalidOperationException("Codestream box not found in JP2 or J2K file.");
}
private static byte ParseCodestream(ReadOnlySpan<byte> codestream)

View File

@ -60,11 +60,11 @@
if (dictionary.TryGet(NameToken.BitsPerComponent, out NumericToken? bitsPerComponentToken))
{
bitsPerComponent = bitsPerComponentToken.Int;
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span));
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetBitsPerComponent(xObject.Stream.Data.Span));
}
else
{
bitsPerComponent = Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span);
bitsPerComponent = Jpeg2000Helper.GetBitsPerComponent(xObject.Stream.Data.Span);
System.Diagnostics.Debug.Assert(new int[] { 1, 2, 4, 8, 16 }.Contains(bitsPerComponent));
}
}