mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Support for JBIG2 (port of Apache's pdfbox-jbig2)
This commit is contained in:
167
src/UglyToad.PdfPig.Tests/Filters/Jbig2/ArithmeticDecoderTest.cs
Normal file
167
src/UglyToad.PdfPig.Tests/Filters/Jbig2/ArithmeticDecoderTest.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
using Xunit;
|
||||
|
||||
public class ArithmeticDecoderTest
|
||||
{
|
||||
private static readonly long[][] Tracedata = new[]{
|
||||
new []{ 0L, 0x8000L, 0x42638000L, 0x3D9C0000L }, new []{ 0L, 0xAC02L, 0x84C70000L, 0x273A0000L },
|
||||
new []{ 0L, 0xF002L, 0xA18C7600L, 0x4E758800L }, new []{ 0L, 0xD801L, 0x898B7600L, 0x4E758800L },
|
||||
new []{ 0L, 0xC000L, 0x718A7600L, 0x4E758800L }, new []{ 0L, 0xA7FFL, 0x59897600L, 0x4E758800L },
|
||||
new []{ 0L, 0x8FFEL, 0x41887600L, 0x4E758800L }, new []{ 0L, 0xEFFAL, 0x530EEC00L, 0x9CEB1000L },
|
||||
new []{ 0L, 0xE539L, 0x484DEC00L, 0x9CEB1000L }, new []{ 0L, 0xDA78L, 0x3D8CEC00L, 0x9CEB1000L },
|
||||
new []{ 0L, 0xCFB7L, 0x32CBEC00L, 0x9CEB1000L }, new []{ 0L, 0xC4F6L, 0x280AEC00L, 0x9CEB1000L },
|
||||
new []{ 0L, 0xBA35L, 0x1D49EC00L, 0x9CEB1000L }, new []{ 0L, 0xAF74L, 0x1288EC00L, 0x9CEB1000L },
|
||||
new []{ 1L, 0xA4B3L, 0x07C7EC00L, 0x9CEB1000L }, new []{ 0L, 0xAC10L, 0x7C7EC000L, 0x2F910000L },
|
||||
new []{ 0L, 0x900FL, 0x607DC000L, 0x2F910000L }, new []{ 0L, 0xE81CL, 0x88F98000L, 0x5F220000L },
|
||||
new []{ 0L, 0xD21BL, 0x72F88000L, 0x5F220000L }, new []{ 0L, 0xBC1AL, 0x5CF78000L, 0x5F220000L },
|
||||
new []{ 0L, 0xA619L, 0x46F68000L, 0x5F220000L }, new []{ 0L, 0x9018L, 0x30F58000L, 0x5F220000L },
|
||||
new []{ 0L, 0xF42EL, 0x35E90000L, 0xBE440000L }, new []{ 0L, 0xE32DL, 0x24E80000L, 0xBE440000L },
|
||||
new []{ 0L, 0xD22CL, 0x13E70000L, 0xBE440000L }, new []{ 1L, 0xC12BL, 0x02E60000L, 0xBE440000L },
|
||||
new []{ 0L, 0x8808L, 0x1737E000L, 0x70D01800L }, new []{ 1L, 0xE80EL, 0x066DC000L, 0xE1A03000L },
|
||||
new []{ 0L, 0x9008L, 0x336E0000L, 0x5C998000L }, new []{ 0L, 0xF40EL, 0x3ADA0000L, 0xB9330000L },
|
||||
new []{ 0L, 0xE00DL, 0x26D90000L, 0xB9330000L }, new []{ 1L, 0xCC0CL, 0x12D80000L, 0xB9330000L },
|
||||
new []{ 0L, 0xA008L, 0x96C70800L, 0x0940F000L }, new []{ 0L, 0x8807L, 0x7EC60800L, 0x0940F000L },
|
||||
new []{ 0L, 0xE00CL, 0xCD8A1000L, 0x1281E000L }, new []{ 0L, 0xCA0BL, 0xB7891000L, 0x1281E000L },
|
||||
new []{ 0L, 0xB40AL, 0xA1881000L, 0x1281E000L }, new []{ 0L, 0x9E09L, 0x8B871000L, 0x1281E000L },
|
||||
new []{ 0L, 0x8808L, 0x75861000L, 0x1281E000L }, new []{ 0L, 0xE40EL, 0xBF0A2000L, 0x2503C000L },
|
||||
new []{ 0L, 0xD00DL, 0xAB092000L, 0x2503C000L }, new []{ 0L, 0xBC0CL, 0x97082000L, 0x2503C000L },
|
||||
new []{ 0L, 0xA80BL, 0x83072000L, 0x2503C000L }, new []{ 0L, 0x940AL, 0x6F062000L, 0x2503C000L },
|
||||
new []{ 0L, 0x8009L, 0x5B052000L, 0x2503C000L }, new []{ 0L, 0xD810L, 0x8E084000L, 0x4A078000L },
|
||||
new []{ 0L, 0xC60FL, 0x7C074000L, 0x4A078000L }, new []{ 0L, 0xB40EL, 0x6A064000L, 0x4A078000L },
|
||||
new []{ 0L, 0xA20DL, 0x58054000L, 0x4A078000L }, new []{ 0L, 0x900CL, 0x46044000L, 0x4A078000L },
|
||||
new []{ 0L, 0xFC16L, 0x68068000L, 0x940F0000L }, new []{ 0L, 0xEB15L, 0x57058000L, 0x940F0000L },
|
||||
new []{ 0L, 0xDA14L, 0x46048000L, 0x940F0000L }, new []{ 0L, 0xC913L, 0x35038000L, 0x940F0000L },
|
||||
new []{ 0L, 0xB812L, 0x24028000L, 0x940F0000L }, new []{ 0L, 0xA711L, 0x13018000L, 0x940F0000L },
|
||||
new []{ 1L, 0x9610L, 0x02008000L, 0x940F0000L }, new []{ 1L, 0x8808L, 0x10068400L, 0x78017800L },
|
||||
new []{ 0L, 0xA008L, 0x80342000L, 0x1FD3C000L }, new []{ 0L, 0x8807L, 0x68332000L, 0x1FD3C000L },
|
||||
new []{ 0L, 0xE00CL, 0xA0644000L, 0x3FA78000L }, new []{ 0L, 0xCA0BL, 0x8A634000L, 0x3FA78000L },
|
||||
new []{ 0L, 0xB40AL, 0x74624000L, 0x3FA78000L }, new []{ 0L, 0x9E09L, 0x5E614000L, 0x3FA78000L },
|
||||
new []{ 0L, 0x8808L, 0x48604000L, 0x3FA78000L }, new []{ 0L, 0xE40EL, 0x64BE8000L, 0x7F4F0000L },
|
||||
new []{ 0L, 0xD00DL, 0x50BD8000L, 0x7F4F0000L }, new []{ 0L, 0xBC0CL, 0x3CBC8000L, 0x7F4F0000L },
|
||||
new []{ 0L, 0xA80BL, 0x28BB8000L, 0x7F4F0000L }, new []{ 0L, 0x940AL, 0x14BA8000L, 0x7F4F0000L },
|
||||
new []{ 1L, 0x8009L, 0x00B98000L, 0x7F4F0000L }, new []{ 1L, 0xA008L, 0x05CD0C00L, 0x9A3AF000L },
|
||||
new []{ 0L, 0xC008L, 0x2E686000L, 0x919F8000L }, new []{ 1L, 0x9E07L, 0x0C676000L, 0x919F8000L },
|
||||
new []{ 0L, 0x8804L, 0x319D8000L, 0x56660000L }, new []{ 1L, 0xC006L, 0x13390000L, 0xACCC0000L },
|
||||
new []{ 0L, 0x9004L, 0x4CE41000L, 0x431FEC00L }, new []{ 0L, 0xC006L, 0x39C62000L, 0x863FD800L },
|
||||
new []{ 1L, 0x9805L, 0x11C52000L, 0x863FD800L }, new []{ 0L, 0xA004L, 0x47148000L, 0x58EF6000L },
|
||||
new []{ 1L, 0xD806L, 0x26270000L, 0xB1DEC000L }, new []{ 0L, 0xC004L, 0x989C0000L, 0x27670000L },
|
||||
new []{ 0L, 0x8C03L, 0x649B0000L, 0x27670000L }, new []{ 0L, 0xB004L, 0x61340400L, 0x4ECFFA00L },
|
||||
new []{ 0L, 0x8003L, 0x31330400L, 0x4ECFFA00L }, new []{ 1L, 0xA004L, 0x02640800L, 0x9D9FF400L },
|
||||
new []{ 1L, 0xA004L, 0x09902000L, 0x9673D000L }, new []{ 1L, 0xD004L, 0x26408000L, 0xA9C34000L },
|
||||
new []{ 0L, 0xE004L, 0x99020000L, 0x47010000L }, new []{ 0L, 0x9803L, 0x51010000L, 0x47010000L },
|
||||
new []{ 1L, 0xA004L, 0x12004000L, 0x8E03BE00L }, new []{ 0L, 0xE004L, 0x48010000L, 0x9802F800L },
|
||||
new []{ 1L, 0x9803L, 0x00000000L, 0x9802F800L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001F000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201E000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001C000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA2018000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x90010000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201FE00L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001FC00L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201F800L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001F000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201E000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001C000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA2018000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x90010000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201FE00L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001FC00L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201F800L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001F000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA201E000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x9001C000L },
|
||||
new []{ 1L, 0xA202L, 0x00000000L, 0xA2018000L }, new []{ 0L, 0x9002L, 0x00000000L, 0x90010000L },
|
||||
new []{ 1L, 0xA202L, 0x00008200L, 0xA2017C00L }, new []{ 0L, 0x9002L, 0x00010400L, 0x9000F800L },
|
||||
new []{ 1L, 0xA202L, 0x00020800L, 0xA1FFF000L }, new []{ 0L, 0x9002L, 0x00041000L, 0x8FFDE000L },
|
||||
new []{ 1L, 0xA202L, 0x00082000L, 0xA1F9C000L }, new []{ 0L, 0x9002L, 0x00104000L, 0x8FF18000L },
|
||||
new []{ 1L, 0xA202L, 0x00208000L, 0xA1E10000L }, new []{ 0L, 0x9002L, 0x00410000L, 0x8FC00000L },
|
||||
new []{ 1L, 0xA202L, 0x00821A00L, 0xA17FE400L }, new []{ 0L, 0x9002L, 0x01043400L, 0x8EFDC800L },
|
||||
new []{ 1L, 0xA202L, 0x02086800L, 0x9FF99000L }, new []{ 0L, 0x9002L, 0x0410D000L, 0x8BF12000L },
|
||||
new []{ 1L, 0xA202L, 0x0821A000L, 0x99E04000L }, new []{ 0L, 0x9002L, 0x10434000L, 0x7FBE8000L },
|
||||
new []{ 1L, 0xA202L, 0x20868000L, 0x817B0000L }, new []{ 0L, 0x9002L, 0x410D0000L, 0x4EF40000L },
|
||||
new []{ 0L, 0xA202L, 0x821B7600L, 0x1FE68800L }, new []{ 0L, 0xB402L, 0x7434EC00L, 0x3FCD1000L },
|
||||
new []{ 0L, 0xF802L, 0x7867D800L, 0x7F9A2000L }, new []{ 0L, 0xC401L, 0x4466D800L, 0x7F9A2000L },
|
||||
new []{ 1L, 0x9000L, 0x1065D800L, 0x7F9A2000L }, new []{ 0L, 0xD004L, 0x41976000L, 0x8E6C8000L },
|
||||
new []{ 1L, 0x9803L, 0x09966000L, 0x8E6C8000L }, new []{ 1L, 0xE004L, 0x26598000L, 0xB9AA0000L },
|
||||
new []{ 0L, 0x9002L, 0x4CB30000L, 0x434E0000L }, new []{ 0L, 0xA202L, 0x99670C00L, 0x089AF200L },
|
||||
new []{ 0L, 0xB402L, 0xA2CC1800L, 0x1135E400L }, new []{ 0L, 0xF802L, 0xD5963000L, 0x226BC800L },
|
||||
new []{ 0L, 0xC401L, 0xA1953000L, 0x226BC800L }, new []{ 0L, 0x9000L, 0x6D943000L, 0x226BC800L },
|
||||
new []{ 0L, 0xB7FEL, 0x73266000L, 0x44D79000L }, new []{ 0L, 0x87FDL, 0x43256000L, 0x44D79000L },
|
||||
new []{ 1L, 0xAFF8L, 0x2648C000L, 0x89AF2000L }, new []{ 0L, 0xA004L, 0x99230000L, 0x06E08000L },
|
||||
new []{ 0L, 0xD806L, 0xCA440000L, 0x0DC10000L }, new []{ 0L, 0xA805L, 0x9A430000L, 0x0DC10000L },
|
||||
new []{ 0L, 0xF008L, 0xD485E800L, 0x1B821600L }, new []{ 0L, 0xC807L, 0xAC84E800L, 0x1B821600L },
|
||||
new []{ 0L, 0xA006L, 0x8483E800L, 0x1B821600L }, new []{ 0L, 0xF00AL, 0xB905D000L, 0x37042C00L },
|
||||
new []{ 0L, 0xCC09L, 0x9504D000L, 0x37042C00L }, new []{ 0L, 0xA808L, 0x7103D000L, 0x37042C00L },
|
||||
new []{ 0L, 0x8407L, 0x4D02D000L, 0x37042C00L }, new []{ 0L, 0xC00CL, 0x5203A000L, 0x6E085800L },
|
||||
new []{ 0L, 0x9E0BL, 0x3002A000L, 0x6E085800L }, new []{ 0L, 0xF814L, 0x1C034000L, 0xDC10B000L },
|
||||
new []{ 1L, 0xDC13L, 0x00024000L, 0xDC10B000L }, new []{ 1L, 0xE008L, 0x00120000L, 0xDFF58000L },
|
||||
new []{ 1L, 0x9004L, 0x00486200L, 0x8FBB9C00L }, new []{ 1L, 0xC004L, 0x01218800L, 0xBEE27000L },
|
||||
new []{ 1L, 0xD004L, 0x04862000L, 0xCB7DC000L }, new []{ 1L, 0xE004L, 0x12188000L, 0xCDEB0000L },
|
||||
new []{ 0L, 0x9002L, 0x24310000L, 0x6BD00000L }, new []{ 0L, 0xA202L, 0x4862FE00L, 0x599F0000L },
|
||||
new []{ 1L, 0xB402L, 0x00C3FC00L, 0xB33E0000L }, new []{ 1L, 0xE004L, 0x030FF000L, 0xDCF40000L },
|
||||
new []{ 0L, 0x9002L, 0x061FE000L, 0x89E20000L }, new []{ 1L, 0xA202L, 0x0C3FC000L, 0x95C20000L },
|
||||
new []{ 0L, 0x9002L, 0x187F8000L, 0x77820000L }, new []{ 1L, 0xA202L, 0x30FF0000L, 0x71020000L },
|
||||
new []{ 1L, 0x9002L, 0x61FFFE00L, 0x2E020000L }, new []{ 1L, 0xFC04L, 0x43FBF800L, 0xB8080000L },
|
||||
new []{ 1L, 0xA802L, 0x87F7F000L, 0x200A0000L }, new []{ 0L, 0xA402L, 0x63EDE000L, 0x40140000L },
|
||||
new []{ 0L, 0x9C02L, 0x1BD9C000L, 0x80280000L }, new []{ 1L, 0xAC02L, 0x37B38000L, 0x744E0000L },
|
||||
new []{ 1L, 0xA802L, 0x6F670000L, 0x389A0000L }, new []{ 1L, 0xA402L, 0x32CE2000L, 0x7133DC00L },
|
||||
new []{ 1L, 0xAC02L, 0x659C4000L, 0x4665B800L }, new []{ 0L, 0xB002L, 0x23368000L, 0x8CCB7000L },
|
||||
new []{ 1L, 0xA202L, 0x466D0000L, 0x5B94E000L }, new []{ 1L, 0xA802L, 0x8CDA0000L, 0x1B27C000L },
|
||||
new []{ 1L, 0xAE02L, 0x77B20000L, 0x364F8000L }, new []{ 1L, 0xCC02L, 0x5F620000L, 0x6C9F0000L },
|
||||
new []{ 0L, 0x9401L, 0x27610000L, 0x6C9F0000L }, new []{ 1L, 0xE004L, 0x9D87FC00L, 0x427C0000L },
|
||||
new []{ 1L, 0x9803L, 0x5586FC00L, 0x427C0000L }, new []{ 0L, 0xA004L, 0x1B0BF800L, 0x84F80000L },
|
||||
new []{ 1L, 0xE004L, 0x6C2FE000L, 0x73D40000L }, new []{ 0L, 0x9803L, 0x242EE000L, 0x73D40000L },
|
||||
new []{ 1L, 0x9002L, 0x485DC000L, 0x47A40000L }, new []{ 1L, 0xA202L, 0x90BB8000L, 0x11460000L },
|
||||
new []{ 1L, 0xB402L, 0x91750000L, 0x228C0000L }, new []{ 1L, 0xF802L, 0xB2E8DC00L, 0x45192000L },
|
||||
new []{ 1L, 0xC401L, 0x7EE7DC00L, 0x45192000L }, new []{ 1L, 0x9000L, 0x4AE6DC00L, 0x45192000L },
|
||||
new []{ 0L, 0xB7FEL, 0x2DCBB800L, 0x8A324000L }, new []{ 1L, 0xC004L, 0xB72EE000L, 0x08D50000L },
|
||||
new []{ 1L, 0x8C03L, 0x832DE000L, 0x08D50000L }, new []{ 1L, 0xB004L, 0x9E59C000L, 0x11AA0000L },
|
||||
new []{ 1L, 0x8003L, 0x6E58C000L, 0x11AA0000L }, new []{ 1L, 0xA004L, 0x7CAF8000L, 0x23540000L },
|
||||
new []{ 1L, 0xF006L, 0xA95D0000L, 0x46A80000L }, new []{ 1L, 0xCC05L, 0x855C0000L, 0x46A80000L },
|
||||
new []{ 1L, 0xA804L, 0x615B0000L, 0x46A80000L }, new []{ 1L, 0x8403L, 0x3D5A0000L, 0x46A80000L },
|
||||
new []{ 1L, 0xC004L, 0x32B28E00L, 0x8D517000L }, new []{ 0L, 0x9E03L, 0x10B18E00L, 0x8D517000L },
|
||||
new []{ 1L, 0x8804L, 0x42C63800L, 0x453DC000L }, new []{ 1L, 0xC006L, 0x358A7000L, 0x8A7B8000L },
|
||||
new []{ 0L, 0x9C05L, 0x11897000L, 0x8A7B8000L }, new []{ 1L, 0x9004L, 0x4625C000L, 0x49DE0000L },
|
||||
new []{ 1L, 0xC006L, 0x2C498000L, 0x93BC0000L }, new []{ 0L, 0x9805L, 0x04488000L, 0x93BC0000L },
|
||||
new []{ 0L, 0xA004L, 0x11223400L, 0x8EE1CA00L }, new []{ 1L, 0xD004L, 0x4488D000L, 0x8B7B2800L },
|
||||
new []{ 0L, 0x9803L, 0x0C87D000L, 0x8B7B2800L }, new []{ 0L, 0xE004L, 0x321F4000L, 0xADE4A000L },
|
||||
new []{ 0L, 0x9002L, 0x643E8000L, 0x2BC34000L }, new []{ 0L, 0xFC04L, 0x4CF60000L, 0xAF0D0000L },
|
||||
new []{ 0L, 0xA802L, 0x99EDB600L, 0x0E144800L }, new []{ 1L, 0xA402L, 0x87D96C00L, 0x1C289000L },
|
||||
new []{ 0L, 0x9C02L, 0x63B0D800L, 0x38512000L }, new []{ 0L, 0x8C02L, 0x1B5FB000L, 0x70A24000L },
|
||||
new []{ 1L, 0xAC02L, 0x36BF6000L, 0x75428000L }, new []{ 1L, 0xA802L, 0x6D7EC000L, 0x3A830000L },
|
||||
new []{ 1L, 0xA402L, 0x2EFB8000L, 0x75060000L }, new []{ 1L, 0xAC02L, 0x5DF70000L, 0x4E0A0000L },
|
||||
new []{ 0L, 0xB002L, 0x13ECD400L, 0x9C152A00L }, new []{ 1L, 0xA202L, 0x27D9A800L, 0x7A285400L },
|
||||
new []{ 0L, 0xA802L, 0x4FB35000L, 0x584EA800L }, new []{ 0L, 0xA202L, 0x9F66A000L, 0x029B5000L },
|
||||
new []{ 0L, 0x9C02L, 0x96CB4000L, 0x0536A000L }, new []{ 1L, 0x8C02L, 0x81948000L, 0x0A6D4000L },
|
||||
new []{ 1L, 0xD804L, 0xAE4E0000L, 0x29B50000L }, new []{ 0L, 0x8203L, 0x584D0000L, 0x29B50000L },
|
||||
new []{ 1L, 0xB008L, 0x09337C00L, 0xA6D48000L }, new []{ 0L, 0xAC02L, 0x1266F800L, 0x999B0000L },
|
||||
new []{ 1L, 0xAC02L, 0x24CDF000L, 0x87340000L }, new []{ 0L, 0xAC02L, 0x499BE000L, 0x62660000L },
|
||||
new []{ 0L, 0xAC02L, 0x9337C000L, 0x18CA0000L }, new []{ 0L, 0xAC02L, 0x7A6D8000L, 0x31940000L },
|
||||
new []{ 1L, 0xB002L, 0x4CD90000L, 0x63280000L }, new []{ 1L, 0xA202L, 0x99B3FE00L, 0x084E0000L },
|
||||
new []{ 1L, 0x9C02L, 0x8B65FC00L, 0x109C0000L }, new []{ 0L, 0x8C02L, 0x6AC9F800L, 0x21380000L },
|
||||
new []{ 1L, 0xD804L, 0x5323E000L, 0x84E00000L }, new []{ 1L, 0xAC02L, 0xA647C000L, 0x05BA0000L },
|
||||
new []{ 1L, 0xAC02L, 0xA08D8000L, 0x0B740000L }, new []{ 1L, 0xB002L, 0x99190000L, 0x16E80000L },
|
||||
new []{ 1L, 0xBE02L, 0x9031FE00L, 0x2DD00000L }, new []{ 1L, 0xEC02L, 0x9061FC00L, 0x5BA00000L }};
|
||||
|
||||
[Fact]
|
||||
public void DecodeTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("arith-encoded-testsequence.bin"));
|
||||
var decoder = new ArithmeticDecoder(iis);
|
||||
|
||||
var cx = new CX(1, 0);
|
||||
for (int i = 0; i < 257; i++)
|
||||
{
|
||||
decoder.Decode(cx);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DecodeTestWithTracadataComparison()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("arith-encoded-testsequence.bin"));
|
||||
var decoder = new ArithmeticDecoder(iis);
|
||||
var cx = new CX(1, 0);
|
||||
|
||||
for (int i = 0; i < 255; i++)
|
||||
{
|
||||
Assert.Equal(Tracedata[i][0], decoder.Decode(cx));
|
||||
Assert.Equal(Tracedata[i + 1][1], decoder.A);
|
||||
Assert.Equal(Tracedata[i + 1][2], decoder.C);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
|
||||
public class ArithmeticIntegerDecoderTest
|
||||
{
|
||||
[Fact]
|
||||
public void DecodeTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("arith-encoded-testsequence.bin"));
|
||||
var ad = new ArithmeticDecoder(iis);
|
||||
var aid = new ArithmeticIntegerDecoder(ad);
|
||||
|
||||
long result = aid.Decode(null);
|
||||
|
||||
Assert.Equal(1, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/UglyToad.PdfPig.Tests/Filters/Jbig2/BitmapTest.cs
Normal file
86
src/UglyToad.PdfPig.Tests/Filters/Jbig2/BitmapTest.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
|
||||
public class BitmapTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetPixelAndSetPixelTest()
|
||||
{
|
||||
var bitmap = new Bitmap(37, 49);
|
||||
Assert.Equal(0, bitmap.GetPixel(3, 19));
|
||||
|
||||
bitmap.SetPixel(3, 19, (byte)1);
|
||||
|
||||
Assert.Equal(1, bitmap.GetPixel(3, 19));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByteAndSetByteTest()
|
||||
{
|
||||
var bitmap = new Bitmap(16, 16);
|
||||
|
||||
byte value = (byte)4;
|
||||
bitmap.SetByte(0, value);
|
||||
bitmap.SetByte(31, value);
|
||||
|
||||
Assert.Equal(value, bitmap.GetByte(0));
|
||||
Assert.Equal(value, bitmap.GetByte(31));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByteThrowsExceptionTest()
|
||||
{
|
||||
var bitmap = new Bitmap(16, 16);
|
||||
|
||||
Action action = () => bitmap.GetByte(32);
|
||||
|
||||
Assert.Throws<IndexOutOfRangeException>(action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetByteThrowsExceptionTest()
|
||||
{
|
||||
var bitmap = new Bitmap(16, 16);
|
||||
|
||||
Action action = () => bitmap.SetByte(32, (byte)0);
|
||||
|
||||
Assert.Throws<IndexOutOfRangeException>(action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByteAsIntegerTest()
|
||||
{
|
||||
var bitmap = new Bitmap(16, 16);
|
||||
|
||||
var byteValue = (byte)4;
|
||||
int integerValue = byteValue;
|
||||
bitmap.SetByte(0, byteValue);
|
||||
bitmap.SetByte(31, byteValue);
|
||||
|
||||
Assert.Equal(integerValue, bitmap.GetByteAsInteger(0));
|
||||
Assert.Equal(integerValue, bitmap.GetByteAsInteger(31));
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHeightTest()
|
||||
{
|
||||
int height = 16;
|
||||
var bitmap = new Bitmap(1, height);
|
||||
|
||||
Assert.Equal(height, bitmap.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetWidthTest()
|
||||
{
|
||||
int width = 16;
|
||||
var bitmap = new Bitmap(width, 1);
|
||||
|
||||
Assert.Equal(width, bitmap.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/UglyToad.PdfPig.Tests/Filters/Jbig2/ChecksumTest.cs
Normal file
94
src/UglyToad.PdfPig.Tests/Filters/Jbig2/ChecksumTest.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
using Xunit;
|
||||
|
||||
public class ChecksumTest
|
||||
{
|
||||
// See: https://jbig2dec.com/tests/index.html
|
||||
public static readonly IEnumerable<object[]> Data =
|
||||
new object[][] {
|
||||
new[] { "042_1.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_2.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_3.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_4.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_5.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_6.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_7.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_8.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_9.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_10.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_11.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_12.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
// NOTE: These two can neither we nor PDFBox decode without error :-(
|
||||
// new[] { "042_13.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
// new[] { "042_14.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_15.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_16.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_17.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_18.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_19.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_20.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_21.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_22.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_23.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_24.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "042_25.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79" },
|
||||
new[] { "amb_1.jb2", "58311272494-318210035-125100-344625-126-79" },
|
||||
new[] { "amb_2.jb2", "58311272494-318210035-125100-344625-126-79" },
|
||||
new[] { "002.jb2", "-12713-4587-92-651657111-57121-1582564895" },
|
||||
new[] { "003.jb2", "-37-108-89-33-78-5019-966-96-124-9675-1-108-24" },
|
||||
new[] { "004.jb2", "-10709436-24-59-48-217114-37-85-3126-24" },
|
||||
new[] { "005.jb2", "712610586-1224021396100112-102-77-1177851" },
|
||||
new[] { "006.jb2", "-8719-116-83-83-35-3425-64-528667602154-25" },
|
||||
new[] { "007.jb2", "6171-125-109-20-128-71925295955793-127-41-122" },
|
||||
new[] { "sampledata.jb2", "104-68-555325117-4757-48527676-9775-8432" },
|
||||
new[] { "sampledata_page1.jb2", "104-68-555325117-4757-48527676-9775-8432" },
|
||||
new[] { "sampledata_page2.jb2", "104-68-555325117-4757-48527676-9775-8432" },
|
||||
new[] { "sampledata_page3.jb2", "-7825-56-41-30-19-719536-3678580-61-2586" },
|
||||
new[] { "20123110001.jb2", "60-96-101-2458-3335024-5468-5-11068-78-80" },
|
||||
new[] { "20123110002.jb2", "-28-921048181-117-48-96126-110-9-2865611113" },
|
||||
new[] { "20123110003.jb2", "-3942-239351-28-56-729169-5839122-439231" },
|
||||
new[] { "20123110004.jb2", "-49-101-28-20-57-4-24-17-9352104-106-118-122-122" },
|
||||
new[] { "20123110005.jb2", "-48221261779-94-838820-127-114110-2-88-80-106" },
|
||||
new[] { "20123110006.jb2", "81-11870-63-30124-1614-45838-53-123-41639" },
|
||||
new[] { "20123110007.jb2", "12183-49124728346-29-124-9-10775-63-44116103" },
|
||||
new[] { "20123110008.jb2", "15-74-49-45958458-67-2545-96-119-122-60100-35" },
|
||||
new[] { "20123110009.jb2", "36115-114-28-123-3-70-87-113-4197-8512396113-65" },
|
||||
new[] { "20123110010.jb2", "-109-1069-61-1576-67-43122406037-75-1091115" },
|
||||
new[] { "PDFjs_8145_p55.jb2", "-116-2412448-85-120-68-48-36-77-1236891-493365" },
|
||||
new[] { "unitized_page_ii.jb2", "-12766-66-96123-7212470-3-116-72114-6462-112" },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Data))]
|
||||
public void CompareChecksum(string filename, string checksum)
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes(filename));
|
||||
var doc = new Jbig2Document(iis);
|
||||
|
||||
Bitmap b = doc.GetPage(1).GetBitmap();
|
||||
byte[] digest = Hash(b.GetByteArray());
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
foreach (byte toAppend in digest)
|
||||
{
|
||||
stringBuilder.Append((sbyte)toAppend);
|
||||
}
|
||||
|
||||
Assert.Equal(checksum, stringBuilder.ToString());
|
||||
}
|
||||
|
||||
private static byte[] Hash(byte[] input)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
return md5.ComputeHash(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/UglyToad.PdfPig.Tests/Filters/Jbig2/GenericRegionTest.cs
Normal file
41
src/UglyToad.PdfPig.Tests/Filters/Jbig2/GenericRegionTest.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
|
||||
public class GenericRegionTest
|
||||
{
|
||||
[Fact]
|
||||
public void ParseHeaderTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("sampledata.jb2"));
|
||||
|
||||
// Twelfth Segment (number 11)
|
||||
var sis = new SubInputStream(iis, 523, 35);
|
||||
var gr = new GenericRegion();
|
||||
gr.Init(null, sis);
|
||||
|
||||
Assert.Equal(54, gr.RegionInfo.BitmapWidth);
|
||||
Assert.Equal(44, gr.RegionInfo.BitmapHeight);
|
||||
Assert.Equal(4, gr.RegionInfo.X);
|
||||
Assert.Equal(11, gr.RegionInfo.Y);
|
||||
Assert.Equal(CombinationOperator.OR, gr.RegionInfo.CombinationOperator);
|
||||
Assert.False(gr.UseExtTemplates);
|
||||
Assert.False(gr.IsMMREncoded);
|
||||
Assert.Equal(0, gr.GbTemplate);
|
||||
Assert.True(gr.IsTPGDon);
|
||||
|
||||
short[] gbAtX = gr.GbAtX;
|
||||
short[] gbAtY = gr.GbAtY;
|
||||
Assert.Equal(3, gbAtX[0]);
|
||||
Assert.Equal(-1, gbAtY[0]);
|
||||
Assert.Equal(-3, gbAtX[1]);
|
||||
Assert.Equal(-1, gbAtY[1]);
|
||||
Assert.Equal(2, gbAtX[2]);
|
||||
Assert.Equal(-2, gbAtY[2]);
|
||||
Assert.Equal(-2, gbAtX[3]);
|
||||
Assert.Equal(-2, gbAtY[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
|
||||
public class HalftoneRegionTest
|
||||
{
|
||||
[Fact]
|
||||
public void ParseHeaderTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("sampledata.jb2"));
|
||||
// Seventh Segment (number 6)
|
||||
var sis = new SubInputStream(iis, 302, 87);
|
||||
var hr = new HalftoneRegion(sis);
|
||||
hr.Init(null, sis);
|
||||
|
||||
Assert.True(hr.IsMMREncoded);
|
||||
Assert.Equal(0, hr.HTemplate);
|
||||
Assert.False(hr.HSkipEnabled);
|
||||
Assert.Equal(CombinationOperator.OR, hr.HCombinationOperator);
|
||||
Assert.Equal(0, hr.HDefaultPixel);
|
||||
Assert.Equal(8, hr.HGridWidth);
|
||||
Assert.Equal(9, hr.HGridHeight);
|
||||
Assert.Equal(0, hr.HGridX);
|
||||
Assert.Equal(0, hr.HGridY);
|
||||
Assert.Equal(1024, hr.HRegionX);
|
||||
Assert.Equal(0, hr.HRegionY);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
|
||||
public class MMRDecompressorTest
|
||||
{
|
||||
[Fact]
|
||||
public void MmrDecodingTest()
|
||||
{
|
||||
var expected = new byte[]
|
||||
{
|
||||
0, 0, 2, 34, 38, 102, 239, 255, 2, 102, 102,
|
||||
238, 238, 239, 255, 255, 0, 2, 102, 102, 127,
|
||||
255, 255, 255, 0, 0, 0, 4, 68, 102, 102, 127
|
||||
};
|
||||
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("sampledata.jb2"));
|
||||
// Sixth Segment (number 5)
|
||||
var sis = new SubInputStream(iis, 252, 38);
|
||||
var mmrd = new MMRDecompressor(16 * 4, 4, sis);
|
||||
Bitmap b = mmrd.Uncompress();
|
||||
byte[] actual = b.GetByteArray();
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using Xunit;
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
|
||||
public class PatternDictionaryTest
|
||||
{
|
||||
[Fact]
|
||||
public void ParseHeaderTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("sampledata.jb2"));
|
||||
// Sixth Segment (number 5)
|
||||
var sis = new SubInputStream(iis, 245, 45);
|
||||
var pd = new PatternDictionary();
|
||||
pd.Init(null, sis);
|
||||
|
||||
Assert.True(pd.IsMMREncoded);
|
||||
Assert.Equal(0, pd.HdTemplate);
|
||||
Assert.Equal(4, pd.HdpWidth);
|
||||
Assert.Equal(4, pd.HdpHeight);
|
||||
Assert.Equal(15, pd.GrayMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters.Jbig2
|
||||
{
|
||||
using UglyToad.PdfPig.Filters.Jbig2;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
using Xunit;
|
||||
|
||||
public class RegionSegmentInformationTest
|
||||
{
|
||||
[Fact]
|
||||
public void ParseHeaderTest()
|
||||
{
|
||||
var iis = new ImageInputStream(ImageHelpers.LoadFileBytes("sampledata.jb2"));
|
||||
var sis = new SubInputStream(iis, 130, 49);
|
||||
var rsi = new RegionSegmentInformation(sis);
|
||||
rsi.ParseHeader();
|
||||
|
||||
Assert.Equal(37, rsi.BitmapWidth);
|
||||
Assert.Equal(8, rsi.BitmapHeight);
|
||||
Assert.Equal(4, rsi.X);
|
||||
Assert.Equal(1, rsi.Y);
|
||||
Assert.Equal(CombinationOperator.OR, rsi.CombinationOperator);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/UglyToad.PdfPig.Tests/Filters/Jbig2DecodeFilterTests.cs
Normal file
50
src/UglyToad.PdfPig.Tests/Filters/Jbig2DecodeFilterTests.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Filters;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Filters
|
||||
{
|
||||
public class Jbig2DecodeFilterTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanDecodeJbig2CompressedImageData_WithoutGlobalSegments()
|
||||
{
|
||||
var encodedImageBytes = ImageHelpers.LoadFileBytes("sampledata_page1.jb2");
|
||||
|
||||
var filter = new Jbig2DecodeFilter();
|
||||
var dictionary = new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.Filter, NameToken.Jbig2Decode }
|
||||
};
|
||||
|
||||
var expectedBytes = ImageHelpers.LoadFileBytes("sampledata_page1.jb2-decoded.bin");
|
||||
var decodedBytes = filter.Decode(encodedImageBytes, new DictionaryToken(dictionary), 0);
|
||||
Assert.Equal(expectedBytes, decodedBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDecodeJbig2CompressedImageData_WithGlobalSegments()
|
||||
{
|
||||
var encodedGlobalsBytes = ImageHelpers.LoadFileBytes("globals.jb2");
|
||||
var encodedImageBytes = ImageHelpers.LoadFileBytes("img-refs-globals.jb2");
|
||||
|
||||
var filter = new Jbig2DecodeFilter();
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Filter, NameToken.Jbig2Decode },
|
||||
{ NameToken.DecodeParms, new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Jbig2Globals, new StreamToken(new DictionaryToken(new Dictionary<NameToken, IToken>()), encodedGlobalsBytes) }
|
||||
})
|
||||
},
|
||||
{ NameToken.ImageMask, BooleanToken.True }
|
||||
};
|
||||
|
||||
var expectedBytes = ImageHelpers.LoadFileBytes("img-refs-globals-decoded.bin", isCompressed: true);
|
||||
var decodedBytes = filter.Decode(encodedImageBytes, new DictionaryToken(dictionary), 0);
|
||||
Assert.Equal(expectedBytes, decodedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/UglyToad.PdfPig.Tests/Images/Files/001.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/001.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/002.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/002.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/003.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/003.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/004.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/004.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/005.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/005.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/006.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/006.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/007.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/007.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_1.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_1.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_10.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_10.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_11.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_11.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_12.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_12.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_13.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_13.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_14.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_14.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_15.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_15.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_16.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_16.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_17.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_17.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_18.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_18.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_19.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_19.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_2.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_2.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_20.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_20.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_21.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_21.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_22.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_22.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_23.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_23.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_24.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_24.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_25.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_25.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_3.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_3.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_4.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_4.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_5.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_5.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_6.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_6.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_7.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_7.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_8.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_8.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_9.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/042_9.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110001.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110001.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110002.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110002.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110003.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110003.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110004.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110004.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110005.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110005.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110006.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110006.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110007.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110007.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110008.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110008.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110009.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110009.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110010.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/20123110010.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/PDFjs_8145_p55.jb2
Executable file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/PDFjs_8145_p55.jb2
Executable file
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
============================================================================
|
||||
The files sampledata_pageN.jb2 and sampledata.jb are included subject to the
|
||||
following terms:
|
||||
============================================================================
|
||||
|
||||
The compliance checking files provided with this package contain
|
||||
information which has been extracted from Recommendation ITU-T T.88
|
||||
"Information technology – Lossy/lossless coding of bi-level images"
|
||||
(2000/02) of the International Telecommunication Union (“ITU Information”),
|
||||
as found in http://www.itu.int/rec/T-REC-T.88-200002-I/en.
|
||||
|
||||
The extraction and use of ITU Information has been made under license
|
||||
from International Telecommunication Union (“ITU”), which owns all property
|
||||
rights (including intellectual property rights) to Recommendation ITU-T
|
||||
T.88 (2000/02). This ITU Information is made available to everyone for free
|
||||
and may be used for non-commercial purposes; for any other use please
|
||||
contact ITU at sales@itu.int. The sole responsibility for extracting the
|
||||
ITU Information and the responsibility for any errors or deficiencies in
|
||||
the package lies exclusively with the PdfPig project. ITU is not
|
||||
involved in the development of the package or the extraction and use of
|
||||
ITU Information contained therein.
|
||||
BIN
src/UglyToad.PdfPig.Tests/Images/Files/amb_1.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/amb_1.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/amb_2.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/amb_2.jb2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/globals.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/globals.jb2
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/img-refs-globals.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/img-refs-globals.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page1.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page1.jb2
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page1.jb2.png
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page1.jb2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 893 B |
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page2.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page2.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page3.jb2
Normal file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/sampledata_page3.jb2
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Images/Files/unitized_page_ii.jb2
Executable file
BIN
src/UglyToad.PdfPig.Tests/Images/Files/unitized_page_ii.jb2
Executable file
Binary file not shown.
@@ -258,6 +258,23 @@
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calgray.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromJbig2DecodedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("sampledata_page1.jb2-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceGrayColorSpaceDetails.Instance,
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 64,
|
||||
HeightInSamples = 56,
|
||||
BitsPerComponent = 1
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("sampledata_page1.jb2.png"), bytes));
|
||||
}
|
||||
|
||||
private static byte[] LoadImage(string name)
|
||||
{
|
||||
return ImageHelpers.LoadFileBytes(name);
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Fonts\TrueType\*.ttf" />
|
||||
<None Remove="Fonts\Type1\*.pfa" />
|
||||
<None Remove="Fonts\Type1\*.pfb" />
|
||||
<None Remove="Integration\Documents\*" />
|
||||
<None Remove="Integration\SpecificTestDocuments\*" />
|
||||
<None Remove="Filters\Jbig2\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -133,4 +137,10 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Images\" />
|
||||
<Folder Include="Images\Files\" />
|
||||
<Folder Include="Filters\Jbig2\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
166
src/UglyToad.PdfPig/Filters/Jbig2/AbstractImageInputStream.cs
Normal file
166
src/UglyToad.PdfPig/Filters/Jbig2/AbstractImageInputStream.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
internal abstract class AbstractImageInputStream : IImageInputStream
|
||||
{
|
||||
private readonly Stack<(long streamPos, int bitOffset)> markedPositions = new Stack<(long, int)>();
|
||||
|
||||
private int bitOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract long Length { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract long Position { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Seek(long pos);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int Read();
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int Read(byte[] b, int off, int len);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Read(byte[] b)
|
||||
{
|
||||
return Read(b, 0, b.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int ReadBit()
|
||||
{
|
||||
var savedBitOffset = bitOffset;
|
||||
var b = ReadByte();
|
||||
SetBitOffset(savedBitOffset);
|
||||
|
||||
var bit = (b & (1 << 7 - bitOffset)) != 0;
|
||||
|
||||
bitOffset = (bitOffset + 1) % 8;
|
||||
|
||||
// Rewind if we're still processing the byte
|
||||
if (bitOffset > 0)
|
||||
{
|
||||
Seek(Position - 1);
|
||||
}
|
||||
|
||||
return (byte)(bit ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long ReadBits(int numBits)
|
||||
{
|
||||
if (numBits > 32)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(numBits));
|
||||
}
|
||||
|
||||
long accum = 0L;
|
||||
for (int i = 0; i < numBits; i++)
|
||||
{
|
||||
accum <<= 1; // Shift left one bit to make room
|
||||
var bit = (long)ReadBit();
|
||||
accum |= bit;
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte ReadByte()
|
||||
{
|
||||
var value = Read();
|
||||
if (value == -1)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint ReadUnsignedInt()
|
||||
{
|
||||
var buffer = new byte[4];
|
||||
Read(buffer);
|
||||
|
||||
return BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Mark()
|
||||
{
|
||||
markedPositions.Push((Position, bitOffset));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
if (markedPositions.Count > 0)
|
||||
{
|
||||
var position = markedPositions.Pop();
|
||||
Seek(position.streamPos);
|
||||
bitOffset = position.bitOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long SkipBytes(int n)
|
||||
{
|
||||
var desiredPosition = Position + n;
|
||||
if (desiredPosition > Length)
|
||||
{
|
||||
Seek(Length);
|
||||
return desiredPosition - Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
Seek(desiredPosition);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SkipBits()
|
||||
{
|
||||
if (bitOffset != 0)
|
||||
{
|
||||
bitOffset = 0;
|
||||
Seek(Position + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bit offset to an integer between 0 and 7, inclusive. The byte offset
|
||||
/// within the stream, as returned by getStreamPosition, is left unchanged.
|
||||
/// A value of 0 indicates the most-significant bit, and a value of 7 indicates
|
||||
/// the least significant bit, of the byte being read.
|
||||
/// </summary>
|
||||
/// <param name="bitOffset">the desired offset, as an int between 0 and 7, inclusive.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">thrown if bitOffset is not between 0 and 7, inclusive.</exception>
|
||||
protected void SetBitOffset(int bitOffset)
|
||||
{
|
||||
if (bitOffset < 0 || bitOffset > 7)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bitOffset), "must be betwwen 0 and 7!");
|
||||
}
|
||||
|
||||
this.bitOffset = bitOffset;
|
||||
}
|
||||
|
||||
protected bool IsAtEnd()
|
||||
{
|
||||
return Position == Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/UglyToad.PdfPig/Filters/Jbig2/ArithmeticDecoder.cs
Normal file
186
src/UglyToad.PdfPig/Filters/Jbig2/ArithmeticDecoder.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the arithmetic decoder, described in ISO/IEC 14492:2001 in E.3
|
||||
/// </summary>
|
||||
internal class ArithmeticDecoder
|
||||
{
|
||||
private static readonly int[][] QE = new[]{
|
||||
new []{ 0x5601, 01, 01, 1 }, new []{ 0x3401, 02, 06, 0 }, new []{ 0x1801, 03, 09, 0 },
|
||||
new []{ 0x0AC1, 04, 12, 0 }, new []{ 0x0521, 05, 29, 0 }, new []{ 0x0221, 38, 33, 0 },
|
||||
new []{ 0x5601, 07, 06, 1 }, new []{ 0x5401, 08, 14, 0 }, new []{ 0x4801, 09, 14, 0 },
|
||||
new []{ 0x3801, 10, 14, 0 }, new []{ 0x3001, 11, 17, 0 }, new []{ 0x2401, 12, 18, 0 },
|
||||
new []{ 0x1C01, 13, 20, 0 }, new []{ 0x1601, 29, 21, 0 }, new []{ 0x5601, 15, 14, 1 },
|
||||
new []{ 0x5401, 16, 14, 0 }, new []{ 0x5101, 17, 15, 0 }, new []{ 0x4801, 18, 16, 0 },
|
||||
new []{ 0x3801, 19, 17, 0 }, new []{ 0x3401, 20, 18, 0 }, new []{ 0x3001, 21, 19, 0 },
|
||||
new []{ 0x2801, 22, 19, 0 }, new []{ 0x2401, 23, 20, 0 }, new []{ 0x2201, 24, 21, 0 },
|
||||
new []{ 0x1C01, 25, 22, 0 }, new []{ 0x1801, 26, 23, 0 }, new []{ 0x1601, 27, 24, 0 },
|
||||
new []{ 0x1401, 28, 25, 0 }, new []{ 0x1201, 29, 26, 0 }, new []{ 0x1101, 30, 27, 0 },
|
||||
new []{ 0x0AC1, 31, 28, 0 }, new []{ 0x09C1, 32, 29, 0 }, new []{ 0x08A1, 33, 30, 0 },
|
||||
new []{ 0x0521, 34, 31, 0 }, new []{ 0x0441, 35, 32, 0 }, new []{ 0x02A1, 36, 33, 0 },
|
||||
new []{ 0x0221, 37, 34, 0 }, new []{ 0x0141, 38, 35, 0 }, new []{ 0x0111, 39, 36, 0 },
|
||||
new []{ 0x0085, 40, 37, 0 }, new []{ 0x0049, 41, 38, 0 }, new []{ 0x0025, 42, 39, 0 },
|
||||
new []{ 0x0015, 43, 40, 0 }, new []{ 0x0009, 44, 41, 0 }, new []{ 0x0005, 45, 42, 0 },
|
||||
new []{ 0x0001, 45, 43, 0 }, new []{ 0x5601, 46, 46, 0 } };
|
||||
|
||||
private readonly IImageInputStream iis;
|
||||
|
||||
private int a;
|
||||
private int b;
|
||||
private long c;
|
||||
|
||||
private int ct;
|
||||
private long streamPos0;
|
||||
|
||||
public int A => a;
|
||||
|
||||
public long C => c;
|
||||
|
||||
public ArithmeticDecoder(IImageInputStream iis)
|
||||
{
|
||||
this.iis = iis;
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
streamPos0 = iis.Position;
|
||||
b = iis.Read();
|
||||
|
||||
c = b << 16;
|
||||
|
||||
ByteIn();
|
||||
|
||||
c <<= 7;
|
||||
ct -= 7;
|
||||
a = 0x8000;
|
||||
}
|
||||
|
||||
public int Decode(CX cx)
|
||||
{
|
||||
int d;
|
||||
int qeValue = QE[cx.Cx][0];
|
||||
int icx = cx.Cx;
|
||||
|
||||
a -= qeValue;
|
||||
|
||||
if ((c >> 16) < qeValue)
|
||||
{
|
||||
d = LpsExchange(cx, icx, qeValue);
|
||||
Renormalize();
|
||||
}
|
||||
else
|
||||
{
|
||||
c -= (qeValue << 16);
|
||||
if ((a & 0x8000) == 0)
|
||||
{
|
||||
d = MpsExchange(cx, icx);
|
||||
Renormalize();
|
||||
}
|
||||
else
|
||||
{
|
||||
return cx.Mps;
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
private void ByteIn()
|
||||
{
|
||||
if (iis.Position > streamPos0)
|
||||
{
|
||||
iis.Seek(iis.Position - 1);
|
||||
}
|
||||
|
||||
b = iis.Read();
|
||||
|
||||
if (b == 0xFF)
|
||||
{
|
||||
int b1 = iis.Read();
|
||||
if (b1 > 0x8f)
|
||||
{
|
||||
c += 0xff00;
|
||||
ct = 8;
|
||||
iis.Seek(iis.Position - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
c += b1 << 9;
|
||||
ct = 7;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
b = iis.Read();
|
||||
c += b << 8;
|
||||
ct = 8;
|
||||
}
|
||||
|
||||
c &= 0xffffffffL;
|
||||
}
|
||||
|
||||
private void Renormalize()
|
||||
{
|
||||
do
|
||||
{
|
||||
if (ct == 0)
|
||||
{
|
||||
ByteIn();
|
||||
}
|
||||
|
||||
a <<= 1;
|
||||
c <<= 1;
|
||||
ct--;
|
||||
|
||||
} while ((a & 0x8000) == 0);
|
||||
|
||||
c &= 0xffffffffL;
|
||||
}
|
||||
|
||||
private int MpsExchange(CX cx, int icx)
|
||||
{
|
||||
int mps = cx.Mps;
|
||||
|
||||
if (a < QE[icx][0])
|
||||
{
|
||||
if (QE[icx][3] == 1)
|
||||
{
|
||||
cx.ToggleMps();
|
||||
}
|
||||
|
||||
cx.Cx = QE[icx][2];
|
||||
return 1 - mps;
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.Cx = QE[icx][1];
|
||||
return mps;
|
||||
}
|
||||
}
|
||||
|
||||
private int LpsExchange(CX cx, int icx, int qeValue)
|
||||
{
|
||||
int mps = cx.Mps;
|
||||
|
||||
if (a < qeValue)
|
||||
{
|
||||
cx.Cx = QE[icx][1];
|
||||
a = qeValue;
|
||||
|
||||
return mps;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (QE[icx][3] == 1)
|
||||
{
|
||||
cx.ToggleMps();
|
||||
}
|
||||
|
||||
cx.Cx = QE[icx][2];
|
||||
a = qeValue;
|
||||
return 1 - mps;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/UglyToad.PdfPig/Filters/Jbig2/ArithmeticIntegerDecoder.cs
Normal file
160
src/UglyToad.PdfPig/Filters/Jbig2/ArithmeticIntegerDecoder.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the arithmetic integer decoder, described in ISO/IEC 14492:2001 (Annex A).
|
||||
/// </summary>
|
||||
internal class ArithmeticIntegerDecoder
|
||||
{
|
||||
private readonly ArithmeticDecoder decoder;
|
||||
|
||||
private int prev;
|
||||
|
||||
public ArithmeticIntegerDecoder(ArithmeticDecoder decoder)
|
||||
{
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arithmetic Integer Decoding Procedure, Annex A.2.
|
||||
/// </summary>
|
||||
/// <param name="cxIAx">cxIAx to be decoded</param>
|
||||
/// <returns>Decoded value.</returns>
|
||||
public long Decode(CX cxIAx)
|
||||
{
|
||||
int v = 0;
|
||||
int d, s;
|
||||
|
||||
int bitsToRead;
|
||||
int offset;
|
||||
|
||||
if (cxIAx == null)
|
||||
{
|
||||
cxIAx = new CX(512, 1);
|
||||
}
|
||||
|
||||
prev = 1;
|
||||
|
||||
cxIAx.Index = prev;
|
||||
s = decoder.Decode(cxIAx);
|
||||
SetPrev(s);
|
||||
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
|
||||
if (d == 1)
|
||||
{
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
|
||||
if (d == 1)
|
||||
{
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
|
||||
if (d == 1)
|
||||
{
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
|
||||
if (d == 1)
|
||||
{
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
|
||||
if (d == 1)
|
||||
{
|
||||
bitsToRead = 32;
|
||||
offset = 4436;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsToRead = 12;
|
||||
offset = 340;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsToRead = 8;
|
||||
offset = 84;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsToRead = 6;
|
||||
offset = 20;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsToRead = 4;
|
||||
offset = 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsToRead = 2;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bitsToRead; i++)
|
||||
{
|
||||
cxIAx.Index = prev;
|
||||
d = decoder.Decode(cxIAx);
|
||||
SetPrev(d);
|
||||
v = (v << 1) | d;
|
||||
}
|
||||
|
||||
v += offset;
|
||||
|
||||
if (s == 0)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
else if (s == 1 && v > 0)
|
||||
{
|
||||
return -v;
|
||||
}
|
||||
|
||||
return long.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The IAID decoding procedure, Annex A.3.
|
||||
/// </summary>
|
||||
/// <param name="cxIAID">The contexts and statistics for decoding procedure.</param>
|
||||
/// <param name="symCodeLen">Symbol code length</param>
|
||||
/// <returns>The decoded value</returns>
|
||||
public int DecodeIAID(CX cxIAID, long symCodeLen)
|
||||
{
|
||||
// A.3 1)
|
||||
prev = 1;
|
||||
|
||||
// A.3 2)
|
||||
for (int i = 0; i < symCodeLen; i++)
|
||||
{
|
||||
cxIAID.Index = prev;
|
||||
prev = (prev << 1) | decoder.Decode(cxIAID);
|
||||
}
|
||||
|
||||
// A.3 3) & 4)
|
||||
return (prev - (1 << (int)symCodeLen));
|
||||
}
|
||||
|
||||
private void SetPrev(int bit)
|
||||
{
|
||||
if (prev < 256)
|
||||
{
|
||||
prev = ((prev << 1) | bit) & 0x1ff;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = ((((prev << 1) | bit) & 511) | 256) & 0x1ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/UglyToad.PdfPig/Filters/Jbig2/Bitmap.cs
Normal file
137
src/UglyToad.PdfPig/Filters/Jbig2/Bitmap.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a bi-level image that is organized like a bitmap.
|
||||
/// </summary>
|
||||
internal class Bitmap
|
||||
{
|
||||
// 8 pixels per byte, 0 for white, 1 for black
|
||||
private readonly byte[] bitmap;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the bitmap in pixels.
|
||||
/// </summary>
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The width of the bitmap in pixels.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of bytes used per row.
|
||||
/// </summary>
|
||||
public int RowStride { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of a blank image.
|
||||
/// The image data is stored in a byte array. Each pixels is stored as one bit, so that each byte contains 8 pixel.
|
||||
/// A pixel has by default the value 0 for white and 1 for black.
|
||||
/// Row stride means the amount of bytes per line. It is computed automatically and fills the pad bits with 0.
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
public Bitmap(int width, int height)
|
||||
{
|
||||
Height = height;
|
||||
Width = width;
|
||||
RowStride = (width + 7) >> 3;
|
||||
|
||||
bitmap = new byte[Height * RowStride];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of a pixel specified by the given coordinates.
|
||||
/// By default, the value is 0 for a white pixel and 1 for a black pixel.
|
||||
/// The value is placed in the rightmost bit in the byte.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-coordinate of the pixel.</param>
|
||||
/// <param name="y">The y-coordinate of the pixel.</param>
|
||||
/// <returns>The value of a pixel.</returns>
|
||||
public byte GetPixel(int x, int y)
|
||||
{
|
||||
int byteIndex = GetByteIndex(x, y);
|
||||
int bitOffset = GetBitOffset(x);
|
||||
|
||||
int toShift = 7 - bitOffset;
|
||||
return (byte)((GetByte(byteIndex) >> toShift) & 0x01);
|
||||
}
|
||||
|
||||
public void SetPixel(int x, int y, byte pixelValue)
|
||||
{
|
||||
int byteIndex = GetByteIndex(x, y);
|
||||
int bitOffset = GetBitOffset(x);
|
||||
|
||||
int shift = 7 - bitOffset;
|
||||
|
||||
byte src = bitmap[byteIndex];
|
||||
byte result = (byte)(src | (pixelValue << shift));
|
||||
bitmap[byteIndex] = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the byte that contains the pixel, specified by the
|
||||
/// pixel's x and y coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-coordinate of the pixel.</param>
|
||||
/// <param name="y">The y-coordinate of the pixel.</param>
|
||||
/// <returns>The index of the byte that contains the specified pixel.</returns>
|
||||
public int GetByteIndex(int x, int y)
|
||||
{
|
||||
return y * RowStride + (x >> 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bitmap as a byte array.
|
||||
/// </summary>
|
||||
/// <returns>The byte array of this bitmap.</returns>
|
||||
public byte[] GetByteArray()
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a byte from the bitmap byte array.
|
||||
/// </summary>
|
||||
/// <param name="index">The array index that specifies the position of the wanted byte.</param>
|
||||
/// <returns>The byte at the requested index.</returns>
|
||||
public byte GetByte(int index)
|
||||
{
|
||||
return bitmap[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given value at the given array index position.
|
||||
/// </summary>
|
||||
/// <param name="index">The array index that specifies the position of a byte.</param>
|
||||
/// <param name="value">The byte that should be set.</param>
|
||||
public void SetByte(int index, byte value)
|
||||
{
|
||||
bitmap[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the byte at specified index into an integer and returns the value.
|
||||
/// if the given index is out of bound.
|
||||
/// </summary>
|
||||
/// <param name="index">The array index that specifies the position of the wanted byte.</param>
|
||||
/// <returns>The converted byte at the requested index as an integer.</returns>
|
||||
public int GetByteAsInteger(int index)
|
||||
{
|
||||
return bitmap[index] & 0xff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the offset of the given x coordinate in its byte. The method uses optimized modulo
|
||||
/// operation for a better performance.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate of a pixel.</param>
|
||||
/// <returns>The bit offset of a pixel in its byte.</returns>
|
||||
public int GetBitOffset(int x)
|
||||
{
|
||||
// The same as x % 8.
|
||||
// The rightmost three bits are 1. The value masks all bits upon the value "7".
|
||||
return x & 0x07;
|
||||
}
|
||||
}
|
||||
}
|
||||
299
src/UglyToad.PdfPig/Filters/Jbig2/Bitmaps.cs
Normal file
299
src/UglyToad.PdfPig/Filters/Jbig2/Bitmaps.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
internal static class Bitmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the specified rectangle area of the bitmap.
|
||||
/// </summary>
|
||||
/// <param name="roi">A <see cref="System.Drawing.Rectangle"/> that specifies the requested image section.</param>
|
||||
/// <param name="src">src the given bitmap</param>
|
||||
/// <returns>A <see cref="Bitmap"/> that represents the requested image section.</returns>
|
||||
public static Bitmap Extract(Rectangle roi, Bitmap src)
|
||||
{
|
||||
var dst = new Bitmap(roi.Width, roi.Height);
|
||||
|
||||
int upShift = roi.X & 0x07;
|
||||
int downShift = 8 - upShift;
|
||||
int dstLineStartIdx = 0;
|
||||
|
||||
int padding = (8 - dst.Width & 0x07);
|
||||
int srcLineStartIdx = src.GetByteIndex(roi.X, roi.Y);
|
||||
int srcLineEndIdx = src.GetByteIndex(roi.X + roi.Width - 1, roi.Y);
|
||||
bool usePadding = dst.RowStride == srcLineEndIdx + 1 - srcLineStartIdx;
|
||||
|
||||
for (int y = roi.Y; y < roi.GetMaxY(); y++)
|
||||
{
|
||||
int srcIdx = srcLineStartIdx;
|
||||
int dstIdx = dstLineStartIdx;
|
||||
|
||||
if (srcLineStartIdx == srcLineEndIdx)
|
||||
{
|
||||
var pixels = (byte)(src.GetByte(srcIdx) << upShift);
|
||||
dst.SetByte(dstIdx, Unpad(padding, pixels));
|
||||
}
|
||||
else if (upShift == 0)
|
||||
{
|
||||
for (int x = srcLineStartIdx; x <= srcLineEndIdx; x++)
|
||||
{
|
||||
byte value = src.GetByte(srcIdx++);
|
||||
|
||||
if (x == srcLineEndIdx && usePadding)
|
||||
{
|
||||
value = Unpad(padding, value);
|
||||
}
|
||||
|
||||
dst.SetByte(dstIdx++, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyLine(src, dst, upShift, downShift, padding, srcLineStartIdx, srcLineEndIdx,
|
||||
usePadding, srcIdx, dstIdx);
|
||||
}
|
||||
|
||||
srcLineStartIdx += src.RowStride;
|
||||
srcLineEndIdx += src.RowStride;
|
||||
dstLineStartIdx += dst.RowStride;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method combines two given bytes with an logical operator.
|
||||
/// The JBIG2 Standard specifies 5 possible combinations of bytes.
|
||||
/// Hint: Please take a look at ISO/IEC 14492:2001 (E) for detailed definition
|
||||
/// and description of the operators.
|
||||
/// </summary>
|
||||
/// <param name="value1">The value that should be combined with value2.</param>
|
||||
/// <param name="value2">The value that should be combined with value1.</param>
|
||||
/// <param name="op">The specified combination operator.</param>
|
||||
/// <returns>The combination result.</returns>
|
||||
public static byte CombineBytes(byte value1, byte value2, CombinationOperator op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case CombinationOperator.OR:
|
||||
return (byte)(value2 | value1);
|
||||
|
||||
case CombinationOperator.AND:
|
||||
return (byte)(value2 & value1);
|
||||
|
||||
case CombinationOperator.XOR:
|
||||
return (byte)(value2 ^ value1);
|
||||
|
||||
case CombinationOperator.XNOR:
|
||||
return (byte)~(value1 ^ value2);
|
||||
|
||||
case CombinationOperator.REPLACE:
|
||||
default:
|
||||
// Old value is replaced by new value.
|
||||
return value2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method combines a given bitmap with the current instance.
|
||||
/// Parts of the bitmap to blit that are outside of the target bitmap will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="src">The bitmap that should be combined with the one of the current instance.</param>
|
||||
/// <param name="dst">The destination bitmap.</param>
|
||||
/// <param name="x">The x coordinate where the upper left corner of the bitmap to blit should be positioned.</param>
|
||||
/// <param name="y">The y coordinate where the upper left corner of the bitmap to blit should be positioned.</param>
|
||||
/// <param name="combinationOperator">The combination operator for combining two pixels.</param>
|
||||
public static void Blit(Bitmap src, Bitmap dst, int x, int y, CombinationOperator combinationOperator)
|
||||
{
|
||||
int startLine = 0;
|
||||
int srcStartIdx = 0;
|
||||
int srcEndIdx = (src.RowStride - 1);
|
||||
|
||||
// Ignore those parts of the source bitmap which would be placed outside the target bitmap.
|
||||
if (x < 0)
|
||||
{
|
||||
srcStartIdx = -x;
|
||||
x = 0;
|
||||
}
|
||||
else if (x + src.Width > dst.Width)
|
||||
{
|
||||
srcEndIdx -= (src.Width + x - dst.Width);
|
||||
}
|
||||
|
||||
if (y < 0)
|
||||
{
|
||||
startLine = -y;
|
||||
y = 0;
|
||||
srcStartIdx += src.RowStride;
|
||||
srcEndIdx += src.RowStride;
|
||||
}
|
||||
else if (y + src.Height > dst.Height)
|
||||
{
|
||||
startLine = src.Height + y - dst.Height;
|
||||
}
|
||||
|
||||
int shiftVal1 = x & 0x07;
|
||||
int shiftVal2 = 8 - shiftVal1;
|
||||
|
||||
int padding = src.Width & 0x07;
|
||||
int toShift = shiftVal2 - padding;
|
||||
|
||||
bool useShift = (shiftVal2 & 0x07) != 0;
|
||||
bool specialCase = src.Width <= ((srcEndIdx - srcStartIdx) << 3) + shiftVal2;
|
||||
|
||||
int dstStartIdx = dst.GetByteIndex(x, y);
|
||||
|
||||
int lastLine = Math.Min(src.Height, startLine + dst.Height);
|
||||
|
||||
if (!useShift)
|
||||
{
|
||||
BlitUnshifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx,
|
||||
combinationOperator);
|
||||
}
|
||||
else if (specialCase)
|
||||
{
|
||||
BlitSpecialShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx,
|
||||
toShift, shiftVal1, shiftVal2, combinationOperator);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlitShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, toShift,
|
||||
shiftVal1, shiftVal2, combinationOperator, padding);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyLine(Bitmap src, Bitmap dst, int sourceUpShift, int sourceDownShift,
|
||||
int padding, int firstSourceByteOfLine, int lastSourceByteOfLine, bool usePadding,
|
||||
int sourceOffset, int targetOffset)
|
||||
{
|
||||
for (int x = firstSourceByteOfLine; x < lastSourceByteOfLine; x++)
|
||||
{
|
||||
|
||||
if (sourceOffset + 1 < src.GetByteArray().Length)
|
||||
{
|
||||
bool isLastByte = x + 1 == lastSourceByteOfLine;
|
||||
var value = (byte)(src.GetByte(sourceOffset++) << sourceUpShift
|
||||
| ((int)(uint)(src.GetByte(sourceOffset) & 0xff) >> sourceDownShift));
|
||||
|
||||
if (isLastByte && !usePadding)
|
||||
{
|
||||
value = Unpad(padding, value);
|
||||
}
|
||||
|
||||
dst.SetByte(targetOffset++, value);
|
||||
|
||||
if (isLastByte && usePadding)
|
||||
{
|
||||
value = Unpad(padding,
|
||||
(byte)((src.GetByte(sourceOffset) & 0xff) << sourceUpShift));
|
||||
dst.SetByte(targetOffset, value);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = (byte)(src.GetByte(sourceOffset++) << sourceUpShift & 0xff);
|
||||
dst.SetByte(targetOffset++, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes unnecessary bits from a byte.
|
||||
/// </summary>
|
||||
/// <param name="padding">The amount of unnecessary bits.</param>
|
||||
/// <param name="value">The byte that should be cleaned up.</param>
|
||||
/// <returns>A cleaned byte.</returns>
|
||||
private static byte Unpad(int padding, byte value)
|
||||
{
|
||||
return (byte)(value >> padding << padding);
|
||||
}
|
||||
|
||||
private static void BlitUnshifted(Bitmap src, Bitmap dst, int startLine, int lastLine,
|
||||
int dstStartIdx, int srcStartIdx, int srcEndIdx, CombinationOperator op)
|
||||
{
|
||||
|
||||
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst
|
||||
.RowStride, srcStartIdx += src.RowStride, srcEndIdx += src.RowStride)
|
||||
{
|
||||
int dstIdx = dstStartIdx;
|
||||
|
||||
// Go through the bytes in a line of the Symbol
|
||||
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++)
|
||||
{
|
||||
byte oldByte = dst.GetByte(dstIdx);
|
||||
byte newByte = src.GetByte(srcIdx);
|
||||
dst.SetByte(dstIdx++, CombineBytes(oldByte, newByte, op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlitSpecialShifted(Bitmap src, Bitmap dst, int startLine, int lastLine,
|
||||
int dstStartIdx, int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1,
|
||||
int shiftVal2, CombinationOperator op)
|
||||
{
|
||||
|
||||
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst
|
||||
.RowStride, srcStartIdx += src.RowStride, srcEndIdx += src.RowStride)
|
||||
{
|
||||
short register = 0;
|
||||
int dstIdx = dstStartIdx;
|
||||
|
||||
// Go through the bytes in a line of the Symbol
|
||||
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++)
|
||||
{
|
||||
byte oldByte = dst.GetByte(dstIdx);
|
||||
register = (short)(((int)register | src.GetByteAsInteger(srcIdx)) << shiftVal2);
|
||||
byte newByte = (byte)(register >> 8);
|
||||
|
||||
if (srcIdx == srcEndIdx)
|
||||
{
|
||||
newByte = Unpad(toShift, newByte);
|
||||
}
|
||||
|
||||
dst.SetByte(dstIdx++, CombineBytes(oldByte, newByte, op));
|
||||
register <<= shiftVal1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlitShifted(Bitmap src, Bitmap dst, int startLine, int lastLine,
|
||||
int dstStartIdx, int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1,
|
||||
int shiftVal2, CombinationOperator op, int padding)
|
||||
{
|
||||
|
||||
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst
|
||||
.RowStride, srcStartIdx += src.RowStride, srcEndIdx += src.RowStride)
|
||||
{
|
||||
short register = 0;
|
||||
int dstIdx = dstStartIdx;
|
||||
|
||||
// Go through the bytes in a line of the symbol
|
||||
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++)
|
||||
{
|
||||
byte oldByte = dst.GetByte(dstIdx);
|
||||
register = (short)(((int)register | src.GetByteAsInteger(srcIdx)) << shiftVal2);
|
||||
|
||||
byte newByte = (byte)(register >> 8);
|
||||
dst.SetByte(dstIdx++, CombineBytes(oldByte, newByte, op));
|
||||
|
||||
register <<= shiftVal1;
|
||||
|
||||
if (srcIdx == srcEndIdx)
|
||||
{
|
||||
newByte = (byte)(register >> (8 - shiftVal2));
|
||||
|
||||
if (padding != 0)
|
||||
{
|
||||
newByte = Unpad(8 + toShift, newByte);
|
||||
}
|
||||
|
||||
oldByte = dst.GetByte(dstIdx);
|
||||
dst.SetByte(dstIdx, CombineBytes(oldByte, newByte, op));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/UglyToad.PdfPig/Filters/Jbig2/CX.cs
Normal file
41
src/UglyToad.PdfPig/Filters/Jbig2/CX.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// CX represents the context used by arithmetic decoding and arithmetic integer decoding. It selects the probability
|
||||
/// estimate and statistics used during decoding procedure.
|
||||
/// </summary>
|
||||
internal class CX
|
||||
{
|
||||
private readonly byte[] cx;
|
||||
private readonly byte[] mps;
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public int Cx { get => cx[Index] & 0x7f; set => cx[Index] = (byte)(value & 0x7f); }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the decision. Possible values are 0 or 1.
|
||||
/// </summary>
|
||||
public byte Mps => mps[Index];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CX"/> instance
|
||||
/// </summary>
|
||||
/// <param name="size">Number of context values</param>
|
||||
/// <param name="index">Start index</param>
|
||||
public CX(int size, int index)
|
||||
{
|
||||
Index = index;
|
||||
cx = new byte[size];
|
||||
mps = new byte[size];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the bit in actual "more predictable symbol" array element.
|
||||
/// </summary>
|
||||
public void ToggleMps()
|
||||
{
|
||||
mps[Index] ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/UglyToad.PdfPig/Filters/Jbig2/CombinationOperator.cs
Normal file
34
src/UglyToad.PdfPig/Filters/Jbig2/CombinationOperator.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// This enumeration keeps the available logical operator defined in the JBIG2 ISO standard.
|
||||
/// </summary>
|
||||
internal enum CombinationOperator
|
||||
{
|
||||
OR, AND, XOR, XNOR, REPLACE
|
||||
}
|
||||
|
||||
internal static class CombinationOperators
|
||||
{
|
||||
public static CombinationOperator TranslateOperatorCodeToEnum(short combinationOperatorCode)
|
||||
{
|
||||
switch (combinationOperatorCode)
|
||||
{
|
||||
case 0:
|
||||
return CombinationOperator.OR;
|
||||
|
||||
case 1:
|
||||
return CombinationOperator.AND;
|
||||
|
||||
case 2:
|
||||
return CombinationOperator.XOR;
|
||||
|
||||
case 3:
|
||||
return CombinationOperator.XNOR;
|
||||
|
||||
default:
|
||||
return CombinationOperator.REPLACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/UglyToad.PdfPig/Filters/Jbig2/EncodedTable.cs
Normal file
65
src/UglyToad.PdfPig/Filters/Jbig2/EncodedTable.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a encoded huffman table.
|
||||
/// </summary>
|
||||
internal class EncodedTable : HuffmanTable
|
||||
{
|
||||
private readonly Table table;
|
||||
|
||||
public EncodedTable(Table table)
|
||||
{
|
||||
this.table = table;
|
||||
ParseTable();
|
||||
}
|
||||
|
||||
public void ParseTable()
|
||||
{
|
||||
var sis = table.getSubInputStream();
|
||||
|
||||
var codeTable = new List<Code>();
|
||||
|
||||
int prefLen, rangeLen, rangeLow;
|
||||
int curRangeLow = table.HtLow;
|
||||
|
||||
// Annex B.2 5) - decode table lines
|
||||
while (curRangeLow < table.HtHigh)
|
||||
{
|
||||
prefLen = (int)sis.ReadBits(table.HtPS);
|
||||
rangeLen = (int)sis.ReadBits(table.HtRS);
|
||||
rangeLow = curRangeLow;
|
||||
|
||||
codeTable.Add(new Code(prefLen, rangeLen, rangeLow, false));
|
||||
|
||||
curRangeLow += 1 << rangeLen;
|
||||
}
|
||||
|
||||
// Annex B.2 6)
|
||||
prefLen = (int)sis.ReadBits(table.HtPS);
|
||||
|
||||
// Annex B.2 7) - lower range table line
|
||||
rangeLen = 32;
|
||||
rangeLow = table.HtLow - 1;
|
||||
codeTable.Add(new Code(prefLen, rangeLen, rangeLow, true));
|
||||
|
||||
// Annex B.2 8)
|
||||
prefLen = (int)sis.ReadBits(table.HtPS);
|
||||
|
||||
// Annex B.2 9) - upper range table line
|
||||
rangeLen = 32;
|
||||
rangeLow = table.HtHigh;
|
||||
codeTable.Add(new Code(prefLen, rangeLen, rangeLow, false));
|
||||
|
||||
// Annex B.2 10) - out-of-band table line
|
||||
if (table.HtOutOfBand == 1)
|
||||
{
|
||||
prefLen = (int)sis.ReadBits(table.HtPS);
|
||||
codeTable.Add(new Code(prefLen, -1, -1, false));
|
||||
}
|
||||
|
||||
InitTree(codeTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/UglyToad.PdfPig/Filters/Jbig2/EndOfStripe.cs
Normal file
27
src/UglyToad.PdfPig/Filters/Jbig2/EndOfStripe.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// This segment flags an end of stripe (see JBIG2 ISO standard, 7.4.9).
|
||||
/// </summary>
|
||||
internal class EndOfStripe : ISegmentData
|
||||
{
|
||||
private SubInputStream subInputStream;
|
||||
private int lineNumber;
|
||||
|
||||
private void ParseHeader()
|
||||
{
|
||||
lineNumber = (int)(subInputStream.ReadBits(32) & 0xffffffff);
|
||||
}
|
||||
|
||||
public void Init(SegmentHeader header, SubInputStream sis)
|
||||
{
|
||||
subInputStream = sis;
|
||||
ParseHeader();
|
||||
}
|
||||
|
||||
public int GetLineNumber()
|
||||
{
|
||||
return lineNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/UglyToad.PdfPig/Filters/Jbig2/FixedSizeTable.cs
Normal file
15
src/UglyToad.PdfPig/Filters/Jbig2/FixedSizeTable.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a fixed size huffman table.
|
||||
/// </summary>
|
||||
internal class FixedSizeTable : HuffmanTable
|
||||
{
|
||||
public FixedSizeTable(List<Code> runCodeTable)
|
||||
{
|
||||
InitTree(runCodeTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
882
src/UglyToad.PdfPig/Filters/Jbig2/GenericRefinementRegion.cs
Normal file
882
src/UglyToad.PdfPig/Filters/Jbig2/GenericRefinementRegion.cs
Normal file
@@ -0,0 +1,882 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a generic refinement region and implements the procedure described in JBIG2 ISO standard, 6.3
|
||||
/// and 7.4.7.
|
||||
/// </summary>
|
||||
internal class GenericRefinementRegion : IRegion
|
||||
{
|
||||
public abstract class Template
|
||||
{
|
||||
internal abstract short Form(short c1, short c2, short c3, short c4, short c5);
|
||||
|
||||
internal abstract void SetIndex(CX cx);
|
||||
}
|
||||
|
||||
private class Template0 : Template
|
||||
{
|
||||
internal override sealed short Form(short c1, short c2, short c3, short c4, short c5)
|
||||
{
|
||||
return (short)((c1 << 10) | (c2 << 7) | (c3 << 4) | (c4 << 1) | (int)c5);
|
||||
}
|
||||
|
||||
internal override sealed void SetIndex(CX cx)
|
||||
{
|
||||
// Figure 14, page 22
|
||||
cx.Index = 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
private class Template1 : Template
|
||||
{
|
||||
internal override sealed short Form(short c1, short c2, short c3, short c4, short c5)
|
||||
{
|
||||
return (short)(((c1 & 0x02) << 8) | (c2 << 6) | ((c3 & 0x03) << 4) | (c4 << 1) | (int)c5);
|
||||
}
|
||||
|
||||
internal override sealed void SetIndex(CX cx)
|
||||
{
|
||||
// Figure 15, page 22
|
||||
cx.Index = 0x080;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Template T0 = new Template0();
|
||||
private static readonly Template T1 = new Template1();
|
||||
|
||||
private SubInputStream subInputStream;
|
||||
|
||||
private SegmentHeader segmentHeader;
|
||||
|
||||
// Region segment information flags, 7.4.1
|
||||
public RegionSegmentInformation RegionInfo { get; private set; }
|
||||
|
||||
// Generic refinement region segment flags, 7.4.7.2
|
||||
private bool isTPGROn;
|
||||
private short templateID;
|
||||
|
||||
private Template template;
|
||||
// Generic refinement region segment AT flags, 7.4.7.3
|
||||
private short[] grAtX;
|
||||
private short[] grAtY;
|
||||
|
||||
// Decoded data as pixel values (use row stride/width to wrap line)
|
||||
private Bitmap regionBitmap;
|
||||
|
||||
// Variables for decoding
|
||||
private Bitmap referenceBitmap;
|
||||
private int referenceDX;
|
||||
private int referenceDY;
|
||||
|
||||
private ArithmeticDecoder arithDecoder;
|
||||
private CX cx;
|
||||
|
||||
// If true, AT pixels are not on their nominal location and have to be overridden.
|
||||
private bool @override;
|
||||
private bool[] grAtOverride;
|
||||
|
||||
public GenericRefinementRegion()
|
||||
{
|
||||
}
|
||||
|
||||
public GenericRefinementRegion(SubInputStream subInputStream)
|
||||
{
|
||||
this.subInputStream = subInputStream;
|
||||
this.RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
}
|
||||
|
||||
public GenericRefinementRegion(SubInputStream subInputStream,
|
||||
SegmentHeader segmentHeader)
|
||||
{
|
||||
this.subInputStream = subInputStream;
|
||||
this.segmentHeader = segmentHeader;
|
||||
this.RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the flags described in JBIG2 ISO standard:
|
||||
/// 7.4.7.2 Generic refinement region segment flags.
|
||||
/// 7.4.7.3 Generic refinement refion segment AT flags.
|
||||
/// </summary>
|
||||
private void ParseHeader()
|
||||
{
|
||||
RegionInfo.ParseHeader();
|
||||
|
||||
// Bit 2-7
|
||||
subInputStream.ReadBits(6); // Dirty read...
|
||||
|
||||
// Bit 1
|
||||
if (subInputStream.ReadBit() == 1)
|
||||
{
|
||||
isTPGROn = true;
|
||||
}
|
||||
|
||||
// Bit 0
|
||||
templateID = (short)subInputStream.ReadBit();
|
||||
|
||||
switch (templateID)
|
||||
{
|
||||
case 0:
|
||||
template = T0;
|
||||
ReadAtPixels();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
template = T1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadAtPixels()
|
||||
{
|
||||
grAtX = new short[2];
|
||||
grAtY = new short[2];
|
||||
|
||||
// Byte 0
|
||||
grAtX[0] = (sbyte)subInputStream.ReadByte();
|
||||
// Byte 1
|
||||
grAtY[0] = (sbyte)subInputStream.ReadByte();
|
||||
// Byte 2
|
||||
grAtX[1] = (sbyte)subInputStream.ReadByte();
|
||||
// Byte 3
|
||||
grAtY[1] = (sbyte)subInputStream.ReadByte();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode using a template and arithmetic coding, as described in 6.3.5.6
|
||||
/// </summary>
|
||||
/// <returns>The decoded <see cref="Bitmap"/>.</returns>
|
||||
/// <exception cref="System.IO.IOException">if an underlying IO operation fails</exception>
|
||||
/// <exception cref="InvalidHeaderValueException">if a segment header value is invalid</exception>
|
||||
/// <exception cref="IntegerMaxValueException"> if the maximum value limit of an integer is exceeded</exception>
|
||||
public Bitmap GetRegionBitmap()
|
||||
{
|
||||
if (null == regionBitmap)
|
||||
{
|
||||
// 6.3.5.6 - 1)
|
||||
int isLineTypicalPredicted = 0;
|
||||
|
||||
if (referenceBitmap == null)
|
||||
{
|
||||
// Get the reference bitmap, which is the base of refinement process
|
||||
referenceBitmap = GetGrReference();
|
||||
}
|
||||
|
||||
if (arithDecoder == null)
|
||||
{
|
||||
arithDecoder = new ArithmeticDecoder(subInputStream);
|
||||
}
|
||||
|
||||
if (cx == null)
|
||||
{
|
||||
cx = new CX(8192, 1);
|
||||
}
|
||||
|
||||
// 6.3.5.6 - 2)
|
||||
regionBitmap = new Bitmap(RegionInfo.BitmapWidth, RegionInfo.BitmapHeight);
|
||||
|
||||
if (templateID == 0)
|
||||
{
|
||||
// AT pixel may only occur in template 0
|
||||
UpdateOverride();
|
||||
}
|
||||
|
||||
int paddedWidth = (regionBitmap.Width + 7) & -8;
|
||||
int deltaRefStride = isTPGROn ? -referenceDY * referenceBitmap.RowStride : 0;
|
||||
int yOffset = deltaRefStride + 1;
|
||||
|
||||
// 6.3.5.6 - 3)
|
||||
for (int y = 0; y < regionBitmap.Height; y++)
|
||||
{
|
||||
// 6.3.5.6 - 3 b)
|
||||
if (isTPGROn)
|
||||
{
|
||||
isLineTypicalPredicted ^= DecodeSLTP();
|
||||
}
|
||||
|
||||
if (isLineTypicalPredicted == 0)
|
||||
{
|
||||
// 6.3.5.6 - 3 c)
|
||||
DecodeOptimized(y, regionBitmap.Width, regionBitmap.RowStride,
|
||||
referenceBitmap.RowStride, paddedWidth, deltaRefStride, yOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 6.3.5.6 - 3 d)
|
||||
DecodeTypicalPredictedLine(y, regionBitmap.Width,
|
||||
regionBitmap.RowStride, referenceBitmap.RowStride,
|
||||
paddedWidth, deltaRefStride);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 6.3.5.6 - 4)
|
||||
return regionBitmap;
|
||||
}
|
||||
|
||||
private int DecodeSLTP()
|
||||
{
|
||||
template.SetIndex(cx);
|
||||
return arithDecoder.Decode(cx);
|
||||
}
|
||||
|
||||
private Bitmap GetGrReference()
|
||||
{
|
||||
SegmentHeader[] segments = segmentHeader.GetRtSegments();
|
||||
IRegion region = (IRegion)segments[0].GetSegmentData();
|
||||
|
||||
return region.GetRegionBitmap();
|
||||
}
|
||||
|
||||
private void DecodeOptimized(int lineNumber, int width, int rowStride,
|
||||
int refRowStride, int paddedWidth, int deltaRefStride,
|
||||
int lineOffset)
|
||||
{
|
||||
|
||||
// Offset of the reference bitmap with respect to the bitmap being decoded
|
||||
// For example: if referenceDY = -1, y is 1 HIGHER that currY
|
||||
int currentLine = lineNumber - referenceDY;
|
||||
int referenceByteIndex = referenceBitmap.GetByteIndex(Math.Max(0, -referenceDX),
|
||||
currentLine);
|
||||
|
||||
int byteIndex = regionBitmap.GetByteIndex(Math.Max(0, referenceDX), lineNumber);
|
||||
|
||||
switch (templateID)
|
||||
{
|
||||
case 0:
|
||||
DecodeTemplate(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride,
|
||||
lineOffset, byteIndex, currentLine, referenceByteIndex, T0);
|
||||
break;
|
||||
case 1:
|
||||
DecodeTemplate(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride,
|
||||
lineOffset, byteIndex, currentLine, referenceByteIndex, T1);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DecodeTemplate(int lineNumber, int width, int rowStride,
|
||||
int refRowStride, int paddedWidth, int deltaRefStride,
|
||||
int lineOffset, int byteIndex, int currentLine, int refByteIndex,
|
||||
Template templateFormation)
|
||||
{
|
||||
short c1, c2, c3, c4, c5;
|
||||
|
||||
int w1, w2, w3, w4;
|
||||
w1 = w2 = w3 = w4 = 0;
|
||||
|
||||
if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.Height)
|
||||
{
|
||||
w1 = referenceBitmap.GetByteAsInteger(refByteIndex - refRowStride);
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
w2 = referenceBitmap.GetByteAsInteger(refByteIndex);
|
||||
}
|
||||
|
||||
if (currentLine >= -1 && currentLine + 1 < referenceBitmap.Height)
|
||||
{
|
||||
w3 = referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride);
|
||||
}
|
||||
|
||||
refByteIndex++;
|
||||
|
||||
if (lineNumber >= 1)
|
||||
{
|
||||
w4 = regionBitmap.GetByteAsInteger(byteIndex - rowStride);
|
||||
}
|
||||
|
||||
byteIndex++;
|
||||
|
||||
int modReferenceDX = referenceDX % 8;
|
||||
int shiftOffset = 6 + modReferenceDX;
|
||||
int modRefByteIdx = refByteIndex % refRowStride;
|
||||
|
||||
if (shiftOffset >= 0)
|
||||
{
|
||||
c1 = (short)((shiftOffset >= 8 ? 0 : ((int)((uint)w1 >> shiftOffset))) & 0x07);
|
||||
c2 = (short)((shiftOffset >= 8 ? 0 : ((int)((uint)w2 >> shiftOffset))) & 0x07);
|
||||
c3 = (short)((shiftOffset >= 8 ? 0 : ((int)((uint)w3 >> shiftOffset))) & 0x07);
|
||||
if (shiftOffset == 6 && modRefByteIdx > 1)
|
||||
{
|
||||
if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.Height)
|
||||
{
|
||||
c1 = (short)((int)c1 | (referenceBitmap.GetByteAsInteger(refByteIndex - refRowStride - 2) << 2) & 0x04);
|
||||
}
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
c2 = (short)((int)c2 | (referenceBitmap.GetByteAsInteger(refByteIndex - 2) << 2) & 0x04);
|
||||
}
|
||||
if (currentLine >= -1 && currentLine + 1 < referenceBitmap.Height)
|
||||
{
|
||||
c3 = (short)((int)c3 | (referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride - 2) << 2) & 0x04);
|
||||
}
|
||||
}
|
||||
if (shiftOffset == 0)
|
||||
{
|
||||
w1 = w2 = w3 = 0;
|
||||
if (modRefByteIdx < refRowStride - 1)
|
||||
{
|
||||
if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.Height)
|
||||
{
|
||||
w1 = referenceBitmap.GetByteAsInteger(refByteIndex - refRowStride);
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
w2 = referenceBitmap.GetByteAsInteger(refByteIndex);
|
||||
}
|
||||
|
||||
if (currentLine >= -1 && currentLine + 1 < referenceBitmap.Height)
|
||||
{
|
||||
w3 = referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride);
|
||||
}
|
||||
}
|
||||
refByteIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
c1 = (short)((w1 << 1) & 0x07);
|
||||
c2 = (short)((w2 << 1) & 0x07);
|
||||
c3 = (short)((w3 << 1) & 0x07);
|
||||
w1 = w2 = w3 = 0;
|
||||
if (modRefByteIdx < refRowStride - 1)
|
||||
{
|
||||
if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.Height)
|
||||
{
|
||||
w1 = referenceBitmap.GetByteAsInteger(refByteIndex - refRowStride);
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
w2 = referenceBitmap.GetByteAsInteger(refByteIndex);
|
||||
}
|
||||
|
||||
if (currentLine >= -1 && currentLine + 1 < referenceBitmap.Height)
|
||||
{
|
||||
w3 = referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride);
|
||||
}
|
||||
|
||||
refByteIndex++;
|
||||
}
|
||||
c1 |= (short)((int)((uint)w1 >> 7) & 0x07);
|
||||
c2 |= (short)((int)((uint)w2 >> 7) & 0x07);
|
||||
c3 |= (short)((int)((uint)w3 >> 7) & 0x07);
|
||||
}
|
||||
|
||||
c4 = (short)(int)((uint)w4 >> 6);
|
||||
c5 = 0;
|
||||
|
||||
int modBitsToTrim = (2 - modReferenceDX) % 8;
|
||||
w1 <<= modBitsToTrim;
|
||||
w2 <<= modBitsToTrim;
|
||||
w3 <<= modBitsToTrim;
|
||||
|
||||
w4 <<= 2;
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int minorX = x & 0x07;
|
||||
|
||||
short tval = templateFormation.Form(c1, c2, c3, c4, c5);
|
||||
|
||||
if (@override)
|
||||
{
|
||||
cx.Index = OverrideAtTemplate0(tval, x, lineNumber,
|
||||
regionBitmap.GetByte(regionBitmap.GetByteIndex(x, lineNumber)), minorX);
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.Index = tval;
|
||||
}
|
||||
int bit = arithDecoder.Decode(cx);
|
||||
regionBitmap.SetPixel(x, lineNumber, (byte)bit);
|
||||
|
||||
c1 = (short)(((c1 << 1) | 0x01 & ((int)((uint)w1 >> 7))) & 0x07);
|
||||
c2 = (short)(((c2 << 1) | 0x01 & ((int)((uint)w2 >> 7))) & 0x07);
|
||||
c3 = (short)(((c3 << 1) | 0x01 & ((int)((uint)w3 >> 7))) & 0x07);
|
||||
c4 = (short)(((c4 << 1) | 0x01 & ((int)((uint)w4 >> 7))) & 0x07);
|
||||
c5 = (short)bit;
|
||||
|
||||
if ((x - referenceDX) % 8 == 5)
|
||||
{
|
||||
if (((x - referenceDX) / 8) + 1 >= referenceBitmap.RowStride)
|
||||
{
|
||||
w1 = w2 = w3 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentLine >= 1 && (currentLine - 1 < referenceBitmap.Height))
|
||||
{
|
||||
w1 = referenceBitmap.GetByteAsInteger(refByteIndex - refRowStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
w1 = 0;
|
||||
}
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
w2 = referenceBitmap.GetByteAsInteger(refByteIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
w2 = 0;
|
||||
}
|
||||
if (currentLine >= -1 && (currentLine + 1) < referenceBitmap.Height)
|
||||
{
|
||||
w3 = referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
w3 = 0;
|
||||
}
|
||||
}
|
||||
refByteIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
w1 <<= 1;
|
||||
w2 <<= 1;
|
||||
w3 <<= 1;
|
||||
}
|
||||
|
||||
if (minorX == 5 && lineNumber >= 1)
|
||||
{
|
||||
if ((x >> 3) + 1 >= regionBitmap.RowStride)
|
||||
{
|
||||
w4 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
w4 = regionBitmap.GetByteAsInteger(byteIndex - rowStride);
|
||||
}
|
||||
byteIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
w4 <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOverride()
|
||||
{
|
||||
if (grAtX == null || grAtY == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (grAtX.Length != grAtY.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
grAtOverride = new bool[grAtX.Length];
|
||||
|
||||
switch (templateID)
|
||||
{
|
||||
case 0:
|
||||
if (grAtX[0] != -1 && grAtY[0] != -1)
|
||||
{
|
||||
grAtOverride[0] = true;
|
||||
@override = true;
|
||||
}
|
||||
|
||||
if (grAtX[1] != -1 && grAtY[1] != -1)
|
||||
{
|
||||
grAtOverride[1] = true;
|
||||
@override = true;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@override = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeTypicalPredictedLine(int lineNumber, int width,
|
||||
int rowStride, int refRowStride, int paddedWidth,
|
||||
int deltaRefStride)
|
||||
{
|
||||
// Offset of the reference bitmap with respect to the bitmap being
|
||||
// decoded
|
||||
// For example: if grReferenceDY = -1, y is 1 HIGHER that currY
|
||||
int currentLine = lineNumber - referenceDY;
|
||||
int refByteIndex = referenceBitmap.GetByteIndex(0, currentLine);
|
||||
|
||||
int byteIndex = regionBitmap.GetByteIndex(0, lineNumber);
|
||||
|
||||
switch (templateID)
|
||||
{
|
||||
case 0:
|
||||
DecodeTypicalPredictedLineTemplate0(lineNumber, width, rowStride, refRowStride,
|
||||
paddedWidth, deltaRefStride, byteIndex, currentLine, refByteIndex);
|
||||
break;
|
||||
case 1:
|
||||
DecodeTypicalPredictedLineTemplate1(lineNumber, width, rowStride, refRowStride,
|
||||
paddedWidth, deltaRefStride, byteIndex, currentLine, refByteIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeTypicalPredictedLineTemplate0(int lineNumber, int width,
|
||||
int rowStride, int refRowStride, int paddedWidth,
|
||||
int deltaRefStride, int byteIndex, int currentLine, int refByteIndex)
|
||||
{
|
||||
int context;
|
||||
int overriddenContext;
|
||||
|
||||
int previousLine;
|
||||
int previousReferenceLine;
|
||||
int currentReferenceLine;
|
||||
int nextReferenceLine;
|
||||
|
||||
if (lineNumber > 0)
|
||||
{
|
||||
previousLine = regionBitmap.GetByteAsInteger(byteIndex - rowStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine > 0 && currentLine <= referenceBitmap.Height)
|
||||
{
|
||||
previousReferenceLine = referenceBitmap
|
||||
.GetByteAsInteger(refByteIndex - refRowStride + deltaRefStride) << 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousReferenceLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
currentReferenceLine = referenceBitmap
|
||||
.GetByteAsInteger(refByteIndex + deltaRefStride) << 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentReferenceLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine > -2 && currentLine < (referenceBitmap.Height - 1))
|
||||
{
|
||||
nextReferenceLine = referenceBitmap
|
||||
.GetByteAsInteger(refByteIndex + refRowStride + deltaRefStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextReferenceLine = 0;
|
||||
}
|
||||
|
||||
context = ((previousLine >> 5) & 0x6) | ((nextReferenceLine >> 2) & 0x30)
|
||||
| (currentReferenceLine & 0x180) | (previousReferenceLine & 0xc00);
|
||||
|
||||
int nextByte;
|
||||
for (int x = 0; x < paddedWidth; x = nextByte)
|
||||
{
|
||||
byte result = 0;
|
||||
nextByte = x + 8;
|
||||
int minorWidth = width - x > 8 ? 8 : width - x;
|
||||
bool readNextByte = nextByte < width;
|
||||
bool refReadNextByte = nextByte < referenceBitmap.Width;
|
||||
|
||||
int yOffset = deltaRefStride + 1;
|
||||
|
||||
if (lineNumber > 0)
|
||||
{
|
||||
previousLine = (previousLine << 8) | (readNextByte
|
||||
? regionBitmap.GetByteAsInteger(byteIndex - rowStride + 1) : 0);
|
||||
}
|
||||
|
||||
if (currentLine > 0 && currentLine <= referenceBitmap.Height)
|
||||
{
|
||||
previousReferenceLine = (previousReferenceLine << 8)
|
||||
| (refReadNextByte ? referenceBitmap
|
||||
.GetByteAsInteger(refByteIndex - refRowStride + yOffset) << 4 : 0);
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
currentReferenceLine = (currentReferenceLine << 8) | (refReadNextByte
|
||||
? referenceBitmap.GetByteAsInteger(refByteIndex + yOffset) << 1 : 0);
|
||||
}
|
||||
|
||||
if (currentLine > -2 && currentLine < (referenceBitmap.Height - 1))
|
||||
{
|
||||
nextReferenceLine = (nextReferenceLine << 8) | (refReadNextByte
|
||||
? referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride + yOffset)
|
||||
: 0);
|
||||
}
|
||||
|
||||
for (int minorX = 0; minorX < minorWidth; minorX++)
|
||||
{
|
||||
bool isPixelTypicalPredicted = false;
|
||||
int bit = 0;
|
||||
|
||||
// i)
|
||||
int bitmapValue = (context >> 4) & 0x1FF;
|
||||
|
||||
if (bitmapValue == 0x1ff)
|
||||
{
|
||||
isPixelTypicalPredicted = true;
|
||||
bit = 1;
|
||||
}
|
||||
else if (bitmapValue == 0x00)
|
||||
{
|
||||
isPixelTypicalPredicted = true;
|
||||
bit = 0;
|
||||
}
|
||||
|
||||
if (!isPixelTypicalPredicted)
|
||||
{
|
||||
// iii) - is like 3 c) but for one pixel only
|
||||
if (@override)
|
||||
{
|
||||
overriddenContext = OverrideAtTemplate0(context, x + minorX, lineNumber,
|
||||
result, minorX);
|
||||
cx.Index = overriddenContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.Index = context;
|
||||
}
|
||||
bit = arithDecoder.Decode(cx);
|
||||
}
|
||||
|
||||
int toShift = 7 - minorX;
|
||||
result = (byte)(result | bit << toShift);
|
||||
|
||||
context = ((context & 0xdb6) << 1) | bit | ((previousLine >> toShift + 5) & 0x002)
|
||||
| ((nextReferenceLine >> toShift + 2) & 0x010)
|
||||
| ((currentReferenceLine >> toShift) & 0x080)
|
||||
| ((previousReferenceLine >> toShift) & 0x400);
|
||||
}
|
||||
regionBitmap.SetByte(byteIndex++, result);
|
||||
refByteIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeTypicalPredictedLineTemplate1(int lineNumber, int width,
|
||||
int rowStride, int refRowStride, int paddedWidth,
|
||||
int deltaRefStride, int byteIndex, int currentLine, int refByteIndex)
|
||||
{
|
||||
int context;
|
||||
int grReferenceValue;
|
||||
|
||||
int previousLine;
|
||||
int previousReferenceLine;
|
||||
int currentReferenceLine;
|
||||
int nextReferenceLine;
|
||||
|
||||
if (lineNumber > 0)
|
||||
{
|
||||
previousLine = regionBitmap.GetByteAsInteger(byteIndex - rowStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine > 0 && currentLine <= referenceBitmap.Height)
|
||||
{
|
||||
previousReferenceLine = referenceBitmap
|
||||
.GetByteAsInteger(byteIndex - refRowStride + deltaRefStride) << 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousReferenceLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
currentReferenceLine = referenceBitmap.GetByteAsInteger(byteIndex + deltaRefStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentReferenceLine = 0;
|
||||
}
|
||||
|
||||
if (currentLine > -2 && currentLine < (referenceBitmap.Height - 1))
|
||||
{
|
||||
nextReferenceLine = referenceBitmap
|
||||
.GetByteAsInteger(byteIndex + refRowStride + deltaRefStride);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextReferenceLine = 0;
|
||||
}
|
||||
|
||||
context = ((previousLine >> 5) & 0x6) | ((nextReferenceLine >> 2) & 0x30)
|
||||
| (currentReferenceLine & 0xc0) | (previousReferenceLine & 0x200);
|
||||
|
||||
grReferenceValue = ((nextReferenceLine >> 2) & 0x70) | (currentReferenceLine & 0xc0)
|
||||
| (previousReferenceLine & 0x700);
|
||||
|
||||
int nextByte;
|
||||
for (int x = 0; x < paddedWidth; x = nextByte)
|
||||
{
|
||||
byte result = 0;
|
||||
nextByte = x + 8;
|
||||
int minorWidth = width - x > 8 ? 8 : width - x;
|
||||
bool readNextByte = nextByte < width;
|
||||
bool refReadNextByte = nextByte < referenceBitmap.Width;
|
||||
|
||||
int yOffset = deltaRefStride + 1;
|
||||
|
||||
if (lineNumber > 0)
|
||||
{
|
||||
previousLine = (previousLine << 8) | (readNextByte
|
||||
? regionBitmap.GetByteAsInteger(byteIndex - rowStride + 1) : 0);
|
||||
}
|
||||
|
||||
if (currentLine > 0 && currentLine <= referenceBitmap.Height)
|
||||
{
|
||||
previousReferenceLine = (previousReferenceLine << 8)
|
||||
| (refReadNextByte ? referenceBitmap
|
||||
.GetByteAsInteger(refByteIndex - refRowStride + yOffset) << 2 : 0);
|
||||
}
|
||||
|
||||
if (currentLine >= 0 && currentLine < referenceBitmap.Height)
|
||||
{
|
||||
currentReferenceLine = (currentReferenceLine << 8) | (refReadNextByte
|
||||
? referenceBitmap.GetByteAsInteger(refByteIndex + yOffset) : 0);
|
||||
}
|
||||
|
||||
if (currentLine > -2 && currentLine < (referenceBitmap.Height - 1))
|
||||
{
|
||||
nextReferenceLine = (nextReferenceLine << 8) | (refReadNextByte
|
||||
? referenceBitmap.GetByteAsInteger(refByteIndex + refRowStride + yOffset)
|
||||
: 0);
|
||||
}
|
||||
|
||||
for (int minorX = 0; minorX < minorWidth; minorX++)
|
||||
{
|
||||
int bit;
|
||||
|
||||
// i)
|
||||
int bitmapValue = (grReferenceValue >> 4) & 0x1ff;
|
||||
|
||||
if (bitmapValue == 0x1ff)
|
||||
{
|
||||
bit = 1;
|
||||
}
|
||||
else if (bitmapValue == 0x00)
|
||||
{
|
||||
bit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cx.Index = context;
|
||||
bit = arithDecoder.Decode(cx);
|
||||
}
|
||||
|
||||
int toShift = 7 - minorX;
|
||||
result = (byte)(result | bit << toShift);
|
||||
|
||||
context = ((context & 0x0d6) << 1) | bit | ((previousLine >> toShift + 5) & 0x002)
|
||||
| ((nextReferenceLine >> toShift + 2) & 0x010)
|
||||
| ((currentReferenceLine >> toShift) & 0x040)
|
||||
| ((previousReferenceLine >> toShift) & 0x200);
|
||||
|
||||
grReferenceValue = ((grReferenceValue & 0x0db) << 1)
|
||||
| ((nextReferenceLine >> toShift + 2) & 0x010)
|
||||
| ((currentReferenceLine >> toShift) & 0x080)
|
||||
| ((previousReferenceLine >> toShift) & 0x400);
|
||||
}
|
||||
regionBitmap.SetByte(byteIndex++, result);
|
||||
refByteIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private int OverrideAtTemplate0(int context, int x, int y, int result, int minorX)
|
||||
{
|
||||
if (grAtOverride[0])
|
||||
{
|
||||
context &= 0xfff7;
|
||||
if (grAtY[0] == 0 && grAtX[0] >= -minorX)
|
||||
{
|
||||
context |= (result >> (7 - (minorX + grAtX[0])) & 0x1) << 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
context |= GetPixel(regionBitmap, x + grAtX[0], y + grAtY[0]) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (grAtOverride[1])
|
||||
{
|
||||
context &= 0xefff;
|
||||
if (grAtY[1] == 0 && grAtX[1] >= -minorX)
|
||||
{
|
||||
context |= (result >> (7 - (minorX + grAtX[1])) & 0x1) << 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
context |= GetPixel(referenceBitmap, x + grAtX[1] + referenceDX,
|
||||
y + grAtY[1] + referenceDY) << 12;
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private static byte GetPixel(Bitmap b, int x, int y)
|
||||
{
|
||||
if (x < 0 || x >= b.Width)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (y < 0 || y >= b.Height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return b.GetPixel(x, y);
|
||||
}
|
||||
|
||||
public void Init(SegmentHeader header, SubInputStream sis)
|
||||
{
|
||||
segmentHeader = header;
|
||||
subInputStream = sis;
|
||||
RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
ParseHeader();
|
||||
}
|
||||
|
||||
internal void SetParameters(CX cx, ArithmeticDecoder arithmeticDecoder,
|
||||
short grTemplate, int regionWidth, int regionHeight,
|
||||
Bitmap grReference, int grReferenceDX, int grReferenceDY,
|
||||
bool isTPGRon, short[] grAtX, short[] grAtY)
|
||||
{
|
||||
|
||||
if (null != cx)
|
||||
{
|
||||
this.cx = cx;
|
||||
}
|
||||
|
||||
if (null != arithmeticDecoder)
|
||||
{
|
||||
arithDecoder = arithmeticDecoder;
|
||||
}
|
||||
|
||||
templateID = grTemplate;
|
||||
|
||||
RegionInfo.BitmapWidth = regionWidth;
|
||||
RegionInfo.BitmapHeight = regionHeight;
|
||||
|
||||
referenceBitmap = grReference;
|
||||
referenceDX = grReferenceDX;
|
||||
referenceDY = grReferenceDY;
|
||||
|
||||
isTPGROn = isTPGRon;
|
||||
|
||||
this.grAtX = grAtX;
|
||||
this.grAtY = grAtY;
|
||||
|
||||
regionBitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
1107
src/UglyToad.PdfPig/Filters/Jbig2/GenericRegion.cs
Normal file
1107
src/UglyToad.PdfPig/Filters/Jbig2/GenericRegion.cs
Normal file
File diff suppressed because it is too large
Load Diff
347
src/UglyToad.PdfPig/Filters/Jbig2/HalftoneRegion.cs
Normal file
347
src/UglyToad.PdfPig/Filters/Jbig2/HalftoneRegion.cs
Normal file
@@ -0,0 +1,347 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Util;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the data of segment type "Halftone region". Parsing is described in 7.4.5, page 67. Decoding
|
||||
/// procedure in 6.6.5 and 7.4.5.2.
|
||||
/// </summary>
|
||||
internal class HalftoneRegion : IRegion
|
||||
{
|
||||
private SubInputStream subInputStream;
|
||||
private SegmentHeader segmentHeader;
|
||||
private long dataHeaderOffset = 0;
|
||||
private long dataHeaderLength;
|
||||
private long dataOffset;
|
||||
private long dataLength;
|
||||
|
||||
// Decoded data
|
||||
private Bitmap halftoneRegionBitmap;
|
||||
|
||||
// Previously decoded data from other regions or dictionaries, stored to use as patterns in this region.
|
||||
private List<Bitmap> patterns;
|
||||
|
||||
// Region segment information field, 7.4.1
|
||||
public RegionSegmentInformation RegionInfo { get; private set; }
|
||||
|
||||
// Halftone segment information field, 7.4.5.1.1
|
||||
public byte HDefaultPixel { get; private set; }
|
||||
public CombinationOperator HCombinationOperator { get; private set; }
|
||||
public bool HSkipEnabled { get; private set; }
|
||||
public byte HTemplate { get; private set; }
|
||||
public bool IsMMREncoded { get; private set; }
|
||||
|
||||
// Halftone grid position and size, 7.4.5.1.2
|
||||
// Width of the gray-scale image, 7.4.5.1.2.1
|
||||
public int HGridWidth { get; private set; }
|
||||
// Height of the gray-scale image, 7.4.5.1.2.2
|
||||
public int HGridHeight { get; private set; }
|
||||
// Horizontal offset of the grid, 7.4.5.1.2.3
|
||||
public int HGridX { get; private set; }
|
||||
// Vertical offset of the grid, 7.4.5.1.2.4
|
||||
public int HGridY { get; private set; }
|
||||
|
||||
// Halftone grid vector, 7.4.5.1.3
|
||||
// Horizontal coordinate of the halftone grid vector, 7.4.5.1.3.1
|
||||
public int HRegionX { get; private set; }
|
||||
// Vertical coordinate of the halftone grod vector, 7.4.5.1.3.2
|
||||
public int HRegionY { get; private set; }
|
||||
|
||||
public HalftoneRegion()
|
||||
{
|
||||
}
|
||||
|
||||
public HalftoneRegion(SubInputStream subInputStream)
|
||||
{
|
||||
this.subInputStream = subInputStream;
|
||||
this.RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
}
|
||||
|
||||
public HalftoneRegion(SubInputStream subInputStream, SegmentHeader segmentHeader)
|
||||
{
|
||||
this.subInputStream = subInputStream;
|
||||
this.segmentHeader = segmentHeader;
|
||||
this.RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
}
|
||||
|
||||
private void ParseHeader()
|
||||
{
|
||||
RegionInfo.ParseHeader();
|
||||
|
||||
// Bit 7
|
||||
HDefaultPixel = (byte)subInputStream.ReadBit();
|
||||
|
||||
// Bit 4-6
|
||||
HCombinationOperator = CombinationOperators
|
||||
.TranslateOperatorCodeToEnum((short)(subInputStream.ReadBits(3) & 0xf));
|
||||
|
||||
// Bit 3
|
||||
if (subInputStream.ReadBit() == 1)
|
||||
{
|
||||
HSkipEnabled = true;
|
||||
}
|
||||
|
||||
// Bit 1-2
|
||||
HTemplate = (byte)(subInputStream.ReadBits(2) & 0xf);
|
||||
|
||||
// Bit 0
|
||||
if (subInputStream.ReadBit() == 1)
|
||||
{
|
||||
IsMMREncoded = true;
|
||||
}
|
||||
|
||||
HGridWidth = (int)(subInputStream.ReadBits(32) & 0xffffffff);
|
||||
HGridHeight = (int)(subInputStream.ReadBits(32) & 0xffffffff);
|
||||
|
||||
HGridX = (int)subInputStream.ReadBits(32);
|
||||
HGridY = (int)subInputStream.ReadBits(32);
|
||||
|
||||
HRegionX = (int)subInputStream.ReadBits(16) & 0xffff;
|
||||
HRegionY = (int)subInputStream.ReadBits(16) & 0xffff;
|
||||
|
||||
ComputeSegmentDataStructure();
|
||||
}
|
||||
|
||||
private void ComputeSegmentDataStructure()
|
||||
{
|
||||
dataOffset = subInputStream.Position;
|
||||
dataHeaderLength = dataOffset - dataHeaderOffset;
|
||||
dataLength = subInputStream.Length - dataHeaderLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The procedure is described in JBIG2 ISO standard, 6.6.5.
|
||||
/// </summary>
|
||||
/// <returns>The decoded <see cref="Bitmap"/></returns>
|
||||
public Bitmap GetRegionBitmap()
|
||||
{
|
||||
if (null == halftoneRegionBitmap)
|
||||
{
|
||||
// 6.6.5, page 40
|
||||
// 1)
|
||||
halftoneRegionBitmap = new Bitmap(RegionInfo.BitmapWidth,
|
||||
RegionInfo.BitmapHeight);
|
||||
|
||||
if (patterns == null)
|
||||
{
|
||||
patterns = GetPatterns();
|
||||
}
|
||||
|
||||
if (HDefaultPixel == 1)
|
||||
{
|
||||
ArrayHelper.Fill(halftoneRegionBitmap.GetByteArray(), (byte)0xff);
|
||||
}
|
||||
|
||||
// 2)
|
||||
// 6.6.5.1 Computing hSkip - At the moment SKIP is not used... we are not able to test it.
|
||||
// Bitmap hSkip;
|
||||
// if (hSkipEnabled) {
|
||||
// int hPatternHeight = (int) hPats.get(0).getHeight();
|
||||
// int hPatternWidth = (int) hPats.get(0).getWidth();
|
||||
// Implementation could be achieved like this: Set or get pattern width and height from
|
||||
// referred pattern segments. The method is called like this:
|
||||
// hSkip = computeHSkip(hPatternHeight, hPatternWidth);
|
||||
// }
|
||||
|
||||
// 3)
|
||||
int bitsPerValue = (int)Math.Ceiling(Math.Log(patterns.Count) / Math.Log(2));
|
||||
|
||||
// 4)
|
||||
int[][] grayScaleValues = GrayScaleDecoding(bitsPerValue);
|
||||
|
||||
// 5), rendering the pattern, described in 6.6.5.2
|
||||
RenderPattern(grayScaleValues);
|
||||
}
|
||||
// 6)
|
||||
return halftoneRegionBitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method draws the pattern into the region bitmap({ @code htReg}), as described in 6.6.5.2, page 42
|
||||
/// </summary>
|
||||
private void RenderPattern(int[][] grayScaleValues)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
// 1)
|
||||
for (int m = 0; m < HGridHeight; m++)
|
||||
{
|
||||
// a)
|
||||
for (int n = 0; n < HGridWidth; n++)
|
||||
{
|
||||
// i)
|
||||
x = ComputeX(m, n);
|
||||
y = ComputeY(m, n);
|
||||
|
||||
// ii)
|
||||
Bitmap patternBitmap = patterns[grayScaleValues[m][n]];
|
||||
Bitmaps.Blit(patternBitmap, halftoneRegionBitmap, (x + HGridX), (y + HGridY),
|
||||
HCombinationOperator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Bitmap> GetPatterns()
|
||||
{
|
||||
var patterns = new List<Bitmap>();
|
||||
|
||||
foreach (SegmentHeader s in segmentHeader.GetRtSegments())
|
||||
{
|
||||
PatternDictionary patternDictionary = (PatternDictionary)s.GetSegmentData();
|
||||
patterns.AddRange(patternDictionary.GetDictionary());
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gray-scale image decoding procedure is special for halftone region decoding
|
||||
/// and is described in Annex C.5 on page 98.
|
||||
/// </summary>
|
||||
private int[][] GrayScaleDecoding(int bitsPerValue)
|
||||
{
|
||||
short[] gbAtX = null;
|
||||
short[] gbAtY = null;
|
||||
|
||||
if (!IsMMREncoded)
|
||||
{
|
||||
gbAtX = new short[4];
|
||||
gbAtY = new short[4];
|
||||
// Set AT pixel values
|
||||
if (HTemplate <= 1)
|
||||
{
|
||||
gbAtX[0] = 3;
|
||||
}
|
||||
else if (HTemplate >= 2)
|
||||
{
|
||||
gbAtX[0] = 2;
|
||||
}
|
||||
|
||||
gbAtY[0] = -1;
|
||||
gbAtX[1] = -3;
|
||||
gbAtY[1] = -1;
|
||||
gbAtX[2] = 2;
|
||||
gbAtY[2] = -2;
|
||||
gbAtX[3] = -2;
|
||||
gbAtY[3] = -2;
|
||||
}
|
||||
|
||||
var grayScalePlanes = new Bitmap[bitsPerValue];
|
||||
|
||||
// 1)
|
||||
var genericRegion = new GenericRegion(subInputStream);
|
||||
genericRegion.SetParameters(IsMMREncoded, dataOffset, dataLength, HGridHeight, HGridWidth,
|
||||
HTemplate, false, HSkipEnabled, gbAtX, gbAtY);
|
||||
|
||||
// 2)
|
||||
int j = bitsPerValue - 1;
|
||||
|
||||
grayScalePlanes[j] = genericRegion.GetRegionBitmap();
|
||||
|
||||
while (j > 0)
|
||||
{
|
||||
j--;
|
||||
genericRegion.ResetBitmap();
|
||||
// 3) a)
|
||||
grayScalePlanes[j] = genericRegion.GetRegionBitmap();
|
||||
// 3) b)
|
||||
grayScalePlanes = CombineGrayScalePlanes(grayScalePlanes, j);
|
||||
}
|
||||
|
||||
// 4)
|
||||
return ComputeGrayScaleValues(grayScalePlanes, bitsPerValue);
|
||||
}
|
||||
|
||||
private Bitmap[] CombineGrayScalePlanes(Bitmap[] grayScalePlanes, int j)
|
||||
{
|
||||
int byteIndex = 0;
|
||||
for (int y = 0; y < grayScalePlanes[j].Height; y++)
|
||||
{
|
||||
|
||||
for (int x = 0; x < grayScalePlanes[j].Width; x += 8)
|
||||
{
|
||||
byte newValue = grayScalePlanes[j + 1].GetByte(byteIndex);
|
||||
byte oldValue = grayScalePlanes[j].GetByte(byteIndex);
|
||||
|
||||
grayScalePlanes[j].SetByte(byteIndex++,
|
||||
Bitmaps.CombineBytes(oldValue, newValue, CombinationOperator.XOR));
|
||||
}
|
||||
}
|
||||
return grayScalePlanes;
|
||||
}
|
||||
|
||||
private int[][] ComputeGrayScaleValues(Bitmap[] grayScalePlanes, int bitsPerValue)
|
||||
{
|
||||
// Gray-scale decoding procedure, page 98
|
||||
int[][] grayScaleValues = new int[HGridHeight][];
|
||||
for (int i = 0; i < grayScaleValues.Length; i++)
|
||||
{
|
||||
grayScaleValues[i] = new int[HGridWidth];
|
||||
}
|
||||
|
||||
// 4)
|
||||
for (int y = 0; y < HGridHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < HGridWidth; x += 8)
|
||||
{
|
||||
int minorWidth = HGridWidth - x > 8 ? 8 : HGridWidth - x;
|
||||
int byteIndex = grayScalePlanes[0].GetByteIndex(x, y);
|
||||
|
||||
for (int minorX = 0; minorX < minorWidth; minorX++)
|
||||
{
|
||||
int i = minorX + x;
|
||||
grayScaleValues[y][i] = 0;
|
||||
|
||||
for (int j = 0; j < bitsPerValue; j++)
|
||||
{
|
||||
grayScaleValues[y][i] += ((grayScalePlanes[j]
|
||||
.GetByte(byteIndex) >> (7 - i & 7)) & 1) * (1 << j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return grayScaleValues;
|
||||
}
|
||||
|
||||
private int ComputeX(int m, int n)
|
||||
{
|
||||
return ShiftAndFill((HGridX + m * HRegionY + n * HRegionX));
|
||||
}
|
||||
|
||||
private int ComputeY(int m, int n)
|
||||
{
|
||||
return ShiftAndFill((HGridY + m * HRegionX - n * HRegionY));
|
||||
}
|
||||
|
||||
private int ShiftAndFill(int value)
|
||||
{
|
||||
// shift value by 8 and let the leftmost 8 bits be 0
|
||||
value >>= 8;
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
// fill the leftmost 8 bits with 1
|
||||
int bitPosition = (int)(Math.Log(value.HighestOneBit()) / Math.Log(2));
|
||||
|
||||
for (int i = 1; i < 31 - bitPosition; i++)
|
||||
{
|
||||
// bit flip
|
||||
value |= 1 << (31 - i);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void Init(SegmentHeader header, SubInputStream sis)
|
||||
{
|
||||
segmentHeader = header;
|
||||
subInputStream = sis;
|
||||
RegionInfo = new RegionSegmentInformation(subInputStream);
|
||||
ParseHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
109
src/UglyToad.PdfPig/Filters/Jbig2/HuffmanTable.cs
Normal file
109
src/UglyToad.PdfPig/Filters/Jbig2/HuffmanTable.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// This abstract class is the base class for all types of Huffman tables.
|
||||
/// </summary>
|
||||
internal abstract class HuffmanTable
|
||||
{
|
||||
private readonly InternalNode rootNode = new InternalNode();
|
||||
|
||||
/// <summary>
|
||||
/// This inner class represents a code for use in Huffman tables.
|
||||
/// </summary>
|
||||
internal class Code
|
||||
{
|
||||
public int PrefixLength { get; }
|
||||
public int RangeLength { get; }
|
||||
public int RangeLow { get; }
|
||||
public bool IsLowerRange { get; }
|
||||
|
||||
public int Value { get; set; } = -1;
|
||||
|
||||
public Code(int prefixLength, int rangeLength, int rangeLow, bool isLowerRange)
|
||||
{
|
||||
PrefixLength = prefixLength;
|
||||
RangeLength = rangeLength;
|
||||
RangeLow = rangeLow;
|
||||
IsLowerRange = isLowerRange;
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
return (Value != -1 ? ValueNode.bitPattern(Value, PrefixLength) : "?") + "/"
|
||||
+ PrefixLength + "/" + RangeLength + "/" + RangeLow;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitTree(List<Code> codeTable)
|
||||
{
|
||||
PreprocessCodes(codeTable);
|
||||
|
||||
foreach (var c in codeTable)
|
||||
{
|
||||
rootNode.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
public long Decode(IImageInputStream iis)
|
||||
{
|
||||
return rootNode.Decode(iis);
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
return rootNode + "\n";
|
||||
}
|
||||
|
||||
public static string CodeTableToString(List<Code> codeTable)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in codeTable)
|
||||
{
|
||||
sb.Append(c.ToString()).Append("\n");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void PreprocessCodes(List<Code> codeTable)
|
||||
{
|
||||
// Annex B.3 1) - build the histogram
|
||||
int maxPrefixLength = 0;
|
||||
|
||||
foreach (Code c in codeTable)
|
||||
{
|
||||
maxPrefixLength = Math.Max(maxPrefixLength, c.PrefixLength);
|
||||
}
|
||||
|
||||
var lenCount = new int[maxPrefixLength + 1];
|
||||
foreach (Code c in codeTable)
|
||||
{
|
||||
lenCount[c.PrefixLength]++;
|
||||
}
|
||||
|
||||
int curCode;
|
||||
var firstCode = new int[lenCount.Length + 1];
|
||||
lenCount[0] = 0;
|
||||
|
||||
// Annex B.3 3)
|
||||
for (int curLen = 1; curLen <= lenCount.Length; curLen++)
|
||||
{
|
||||
firstCode[curLen] = (firstCode[curLen - 1] + (lenCount[curLen - 1]) << 1);
|
||||
curCode = firstCode[curLen];
|
||||
foreach (var code in codeTable)
|
||||
{
|
||||
if (code.PrefixLength == curLen)
|
||||
{
|
||||
code.Value = curCode;
|
||||
curCode++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/UglyToad.PdfPig/Filters/Jbig2/IDictionary.cs
Normal file
19
src/UglyToad.PdfPig/Filters/Jbig2/IDictionary.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all JBIG2 dictionaries segments.
|
||||
/// </summary>
|
||||
internal interface IDictionary : ISegmentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Decodes a dictionary segment and returns the result.
|
||||
/// </summary>
|
||||
/// <returns>A list of <see cref="Bitmap"/>s as a result of the decoding process of dictionary segments.</returns>
|
||||
/// <exception cref="InvalidHeaderValueException">if the segment header value is invalid.</exception>
|
||||
/// <exception cref="IntegerMaxValueException">if the maximum value limit of an integer is exceeded.</exception>
|
||||
/// <exception cref="System.IO.IOException">if an underlying IO operation fails.</exception>
|
||||
List<Bitmap> GetDictionary();
|
||||
}
|
||||
}
|
||||
89
src/UglyToad.PdfPig/Filters/Jbig2/IImageInputStream.cs
Normal file
89
src/UglyToad.PdfPig/Filters/Jbig2/IImageInputStream.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
|
||||
internal interface IImageInputStream : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the total length of the stream, if known. Otherwise, -1 is returned.
|
||||
/// </summary>
|
||||
long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current byte position of the stream.
|
||||
/// </summary>
|
||||
long Position { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks a position in the stream to be returned to by a subsequent call to reset. Additionally, calls to mark and reset may be nested arbitrarily.
|
||||
/// An arbitrary amount of data may be read following the call to mark.
|
||||
/// The bit position used by the readBits method is saved and restored by each pair of calls to mark and reset.
|
||||
/// </summary>
|
||||
void Mark();
|
||||
|
||||
/// <summary>
|
||||
/// Reads a single byte from the stream and returns it as an integer between 0 and 255. If the end of the stream is reached, -1 is returned.
|
||||
/// The bit offset within the stream is reset to zero before the read occurs.
|
||||
/// </summary>
|
||||
int Read();
|
||||
|
||||
/// <summary>
|
||||
/// Reads up to b.length bytes from the stream, and stores them into b starting at index 0. The number of bytes read is returned.
|
||||
/// If no bytes can be read because the end of the stream has been reached, -1 is returned.
|
||||
/// The bit offset within the stream is reset to zero before the read occurs.
|
||||
/// </summary>
|
||||
int Read(byte[] b);
|
||||
|
||||
/// <summary>
|
||||
/// Reads up to len bytes from the stream, and stores them into b starting at index off. The number of bytes read is returned. If no bytes can be read because the end of the stream has been reached, -1 is returned.
|
||||
/// The bit offset within the stream is reset to zero before the read occurs.
|
||||
/// </summary>
|
||||
int Read(byte[] b, int off, int len);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a single bit from the stream and returns it as an int with the value 0 or 1. The bit offset is advanced by one and reduced modulo 8.
|
||||
/// </summary>
|
||||
int ReadBit();
|
||||
|
||||
/// <summary>
|
||||
/// Reads a bitstring from the stream and returns it as a long, with the first bit read becoming the most significant bit of the output. The read starts within the byte indicated by getStreamPosition, at the bit given by getBitOffset. The bit offset is advanced by numBits and reduced modulo 8.
|
||||
/// The byte order of the stream has no effect on this method. The return value of this method is constructed as though the bits were read one at a time, and shifted into the right side of the return value,
|
||||
/// </summary>
|
||||
long ReadBits(int numBits);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from the stream and returns it as a byte value. Byte values between 0x00 and 0x7f represent integer values between 0 and 127. Values between 0x80 and 0xff represent negative values from -128 to /1.
|
||||
/// The bit offset within the stream is reset to zero before the read occurs.
|
||||
/// </summary>
|
||||
byte ReadByte();
|
||||
|
||||
/// <summary>
|
||||
/// Reads 4 bytes from the stream, and (conceptually) concatenates them according to the current byte order, converts the result to a long, masks it with 0xffffffffL in order to strip off any sign-extension bits, and returns the result as an unsigned long value.
|
||||
/// The bit offset within the stream is reset to zero before the read occurs
|
||||
/// </summary>
|
||||
uint ReadUnsignedInt();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the stream pointer to its previous position, including the bit offset, at the time of the most recent unmatched call to mark.
|
||||
/// Calls to reset without a corresponding call to mark have no effect.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current stream position to the desired location. The next read will occur at this location. The bit offset is set to 0.
|
||||
/// It is legal to seek past the end of the file; an <see cref="System.IO.EndOfStreamException"/> will be thrown only if a read is performed.
|
||||
/// </summary>
|
||||
void Seek(long pos);
|
||||
|
||||
/// <summary>
|
||||
/// Skips remaining bits in the current byte.
|
||||
/// </summary>
|
||||
void SkipBits();
|
||||
|
||||
/// <summary>
|
||||
/// Moves the stream position forward by a given number of bytes. It is possible that this method will only be able to skip forward by a smaller number of bytes than requested, for example if the end of the stream is reached. In all cases, the actual number of bytes skipped is returned.
|
||||
/// The bit offset is set to zero prior to advancing the position.
|
||||
/// </summary>
|
||||
long SkipBytes(int n);
|
||||
}
|
||||
}
|
||||
22
src/UglyToad.PdfPig/Filters/Jbig2/IRegion.cs
Normal file
22
src/UglyToad.PdfPig/Filters/Jbig2/IRegion.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for all JBIG2 region segments.
|
||||
/// </summary>
|
||||
internal interface IRegion : ISegmentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <see cref="RegionSegmentInformation"/> about this region.
|
||||
/// </summary>
|
||||
RegionSegmentInformation RegionInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Decodes and returns a regions content.
|
||||
/// </summary>
|
||||
/// <returns>The decoded region as <see cref="Bitmap"/>.</returns>
|
||||
/// <exception cref="InvalidHeaderValueException">if the segment header value is invalid.</exception>
|
||||
/// <exception cref="IntegerMaxValueException">if the maximum value limit of an integer is exceeded.</exception>
|
||||
/// <exception cref="System.IO.IOException">if an underlying IO operation fails.</exception>
|
||||
Bitmap GetRegionBitmap();
|
||||
}
|
||||
}
|
||||
18
src/UglyToad.PdfPig/Filters/Jbig2/ISegmentData.cs
Normal file
18
src/UglyToad.PdfPig/Filters/Jbig2/ISegmentData.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for all data parts of segments.
|
||||
/// </summary>
|
||||
internal interface ISegmentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse the stream and read information of header.
|
||||
/// </summary>
|
||||
/// <param name="header">The segments' header (to make referred-to segments available in data part).</param>
|
||||
/// <param name="sis">Wrapped { @code ImageInputStream} into {@code SubInputStream}.</param>
|
||||
/// <exception cref="InvalidHeaderValueException">if the segment header value is invalid.</exception>
|
||||
/// <exception cref="IntegerMaxValueException">if the maximum value limit of an integer is exceeded.</exception>
|
||||
/// <exception cref="System.IO.IOException">if an underlying IO operation fails.</exception>
|
||||
void Init(SegmentHeader header, SubInputStream sis);
|
||||
}
|
||||
}
|
||||
75
src/UglyToad.PdfPig/Filters/Jbig2/ImageInputStream.cs
Normal file
75
src/UglyToad.PdfPig/Filters/Jbig2/ImageInputStream.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
internal class ImageInputStream : AbstractImageInputStream
|
||||
{
|
||||
private readonly Stream inner;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed long Length => inner.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed long Position => inner.Position;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ImageInputStream"/> that will read the image data
|
||||
/// from a given byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public ImageInputStream(byte[] bytes)
|
||||
: this(new MemoryStream(bytes ??
|
||||
throw new ArgumentNullException(nameof(bytes))))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ImageInputStream"/> that will read the image data
|
||||
/// from a given <see cref="Stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">the <see cref="Stream"/> to read the image data from.></param>
|
||||
public ImageInputStream(Stream input)
|
||||
{
|
||||
inner = input ?? throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed void Seek(long pos)
|
||||
{
|
||||
SetBitOffset(0);
|
||||
inner.Position = pos;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed int Read()
|
||||
{
|
||||
if (IsAtEnd())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
SetBitOffset(0);
|
||||
return inner.ReadByte();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed int Read(byte[] b, int off, int len)
|
||||
{
|
||||
if (IsAtEnd())
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
SetBitOffset(0);
|
||||
var numBytesRead = inner.Read(b, 0, len);
|
||||
return numBytesRead;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override sealed void Dispose()
|
||||
{
|
||||
inner.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used if the maximum value limit of an integer is exceeded.
|
||||
/// </summary>
|
||||
internal class IntegerMaxValueException : Jbig2Exception
|
||||
{
|
||||
public IntegerMaxValueException()
|
||||
{
|
||||
}
|
||||
|
||||
public IntegerMaxValueException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public IntegerMaxValueException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/UglyToad.PdfPig/Filters/Jbig2/InternalNode.cs
Normal file
137
src/UglyToad.PdfPig/Filters/Jbig2/InternalNode.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using static HuffmanTable;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an internal node of a Huffman tree. It contains two child nodes.
|
||||
/// </summary>
|
||||
internal class InternalNode : Node
|
||||
{
|
||||
private readonly int depth;
|
||||
|
||||
private Node zero;
|
||||
private Node one;
|
||||
|
||||
public InternalNode()
|
||||
{
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
public InternalNode(int depth)
|
||||
{
|
||||
this.depth = depth;
|
||||
}
|
||||
|
||||
public void Append(Code c)
|
||||
{
|
||||
// ignore unused codes
|
||||
if (c.PrefixLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int shift = c.PrefixLength - 1 - depth;
|
||||
|
||||
if (shift < 0)
|
||||
{
|
||||
throw new ArgumentException("Negative shifting is not possible.");
|
||||
}
|
||||
|
||||
int bit = (c.Value >> shift) & 1;
|
||||
if (shift == 0)
|
||||
{
|
||||
if (c.RangeLength == -1)
|
||||
{
|
||||
// the child will be a OutOfBand
|
||||
if (bit == 1)
|
||||
{
|
||||
if (one != null)
|
||||
{
|
||||
throw new InvalidOperationException("already have a OOB for " + c);
|
||||
}
|
||||
|
||||
one = new OutOfBandNode(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (zero != null)
|
||||
{
|
||||
throw new InvalidOperationException("already have a OOB for " + c);
|
||||
}
|
||||
|
||||
zero = new OutOfBandNode(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the child will be a ValueNode
|
||||
if (bit == 1)
|
||||
{
|
||||
if (one != null)
|
||||
{
|
||||
throw new InvalidOperationException("already have a ValueNode for " + c);
|
||||
}
|
||||
|
||||
one = new ValueNode(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (zero != null)
|
||||
{
|
||||
throw new InvalidOperationException("already have a ValueNode for " + c);
|
||||
}
|
||||
|
||||
zero = new ValueNode(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the child will be an InternalNode
|
||||
if (bit == 1)
|
||||
{
|
||||
if (one == null)
|
||||
{
|
||||
one = new InternalNode(depth + 1);
|
||||
} ((InternalNode)one).Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (zero == null)
|
||||
{
|
||||
zero = new InternalNode(depth + 1);
|
||||
} ((InternalNode)zero).Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override sealed long Decode(IImageInputStream iis)
|
||||
{
|
||||
int b = iis.ReadBit();
|
||||
Node n = b == 0 ? zero : one;
|
||||
return n.Decode(iis);
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
var sb = new StringBuilder("\n");
|
||||
|
||||
Pad(sb);
|
||||
sb.Append("0: ").Append(zero).Append("\n");
|
||||
Pad(sb);
|
||||
sb.Append("1: ").Append(one).Append("\n");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void Pad(StringBuilder sb)
|
||||
{
|
||||
for (int i = 0; i < depth; i++)
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class InvalidHeaderValueException : Jbig2Exception
|
||||
{
|
||||
public InvalidHeaderValueException()
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidHeaderValueException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidHeaderValueException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
298
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Document.cs
Normal file
298
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Document.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the document structure with its pages and global segments.
|
||||
/// </summary>
|
||||
internal class Jbig2Document : IDisposable
|
||||
{
|
||||
// ID string in file header, see ISO/IEC 14492:2001, D.4.1
|
||||
private static readonly int[] FILE_HEADER_ID = { 0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
|
||||
// This map contains all pages of this document. The key is the number of the page.
|
||||
private readonly Dictionary<int, Jbig2Page> pages = new Dictionary<int, Jbig2Page>();
|
||||
|
||||
// The length of the file header if exists
|
||||
private short fileHeaderLength = 9;
|
||||
|
||||
/// <summary>
|
||||
/// According to D.4.2 - File header bit 0
|
||||
/// This flag contains information about the file organization:
|
||||
/// 1: for sequential
|
||||
/// 0: for random access
|
||||
/// You can use the constants <see cref="RANDOM"/> and <see cref="SEQUENTIAL"/>.
|
||||
/// </summary>
|
||||
private short organisationType = (short)SEQUENTIAL;
|
||||
|
||||
public static readonly int RANDOM = 0;
|
||||
public static readonly int SEQUENTIAL = 1;
|
||||
|
||||
/// <summary>
|
||||
/// According to D.4.2 - Bit 1
|
||||
/// true: if amount of pages is unknown, amount of pages field is not present.
|
||||
/// false: if there is a field in the file header where the amount of pages can be read
|
||||
/// </summary>
|
||||
public bool IsNumberOfPageUnknown { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// According to D.4.3 - Number of pages field (4 bytes).
|
||||
/// Only present if <see cref="IsNumberOfPageUnknown"/> is false.
|
||||
/// </summary>
|
||||
public int NumberOfPages { get; private set; }
|
||||
|
||||
// Defines whether extended Template is used.
|
||||
private bool gbUseExtTemplate;
|
||||
|
||||
// This is the source data stream wrapped into a SubInputStream.
|
||||
private readonly SubInputStream subInputStream;
|
||||
|
||||
// Holds a load of segments, that aren't associated with a page.
|
||||
private Jbig2Globals globalSegments;
|
||||
|
||||
public Jbig2Document(IImageInputStream input)
|
||||
: this(input, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Jbig2Document(IImageInputStream input, Jbig2Globals globals)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input), " must not be null");
|
||||
}
|
||||
|
||||
subInputStream = new SubInputStream(input, 0, long.MaxValue);
|
||||
globalSegments = globals;
|
||||
|
||||
MapStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the segment with the given segment number considering only segments
|
||||
/// that aren't associated with a page.
|
||||
/// </summary>
|
||||
/// <param name="segmentNumber">The number of the requested segment.</param>
|
||||
/// <returns>The requested <see cref="SegmentHeader"/>.</returns>
|
||||
internal SegmentHeader GetGlobalSegment(int segmentNumber)
|
||||
{
|
||||
if (null != globalSegments)
|
||||
{
|
||||
return globalSegments.GetSegment(segmentNumber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="Jbig2Page"/> specified by the given page number.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">The page number of the requested <see cref="Jbig2Page"/>.</param>
|
||||
/// <returns>The requested <see cref="Jbig2Page"/>.</returns>
|
||||
public Jbig2Page GetPage(int pageNumber)
|
||||
{
|
||||
return pages.ContainsKey(pageNumber) ? pages[pageNumber] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diposes the supplied <see cref="IImageInputStream"/>.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
subInputStream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the amount of pages in this JBIG2 document. If the pages are striped,
|
||||
/// the document will be completely parsed and the amount of pages will be gathered.
|
||||
/// </summary>
|
||||
/// <returns>The amount of pages in this JBIG2 document.</returns>
|
||||
internal int GetAmountOfPages()
|
||||
{
|
||||
if (IsNumberOfPageUnknown || NumberOfPages == 0)
|
||||
{
|
||||
if (pages.Count == 0)
|
||||
{
|
||||
MapStream();
|
||||
}
|
||||
|
||||
return pages.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NumberOfPages;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method maps the stream and stores all segments.
|
||||
/// </summary>
|
||||
private void MapStream()
|
||||
{
|
||||
var segments = new List<SegmentHeader>();
|
||||
|
||||
long offset = 0;
|
||||
int segmentType = 0;
|
||||
|
||||
// Parse the file header if there is one.
|
||||
if (IsFileHeaderPresent())
|
||||
{
|
||||
ParseFileHeader();
|
||||
offset += fileHeaderLength;
|
||||
}
|
||||
|
||||
if (globalSegments == null)
|
||||
{
|
||||
globalSegments = new Jbig2Globals();
|
||||
}
|
||||
|
||||
Jbig2Page page;
|
||||
|
||||
// If organisation type is random-access: walk through the segment headers until EOF segment
|
||||
// appears (specified with segment number 51)
|
||||
while (segmentType != 51 && !ReachedEndOfStream(offset))
|
||||
{
|
||||
var segment = new SegmentHeader(this, subInputStream, offset,
|
||||
organisationType);
|
||||
|
||||
int associatedPage = segment.PageAssociation;
|
||||
segmentType = segment.SegmentType;
|
||||
|
||||
if (associatedPage != 0)
|
||||
{
|
||||
page = GetPage(associatedPage);
|
||||
if (page == null)
|
||||
{
|
||||
page = new Jbig2Page(this, associatedPage);
|
||||
pages[associatedPage] = page;
|
||||
}
|
||||
page.Add(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
globalSegments.AddSegment(segment.SegmentNumber, segment);
|
||||
}
|
||||
segments.Add(segment);
|
||||
|
||||
offset = subInputStream.Position;
|
||||
|
||||
// Sequential organization skips data part and sets the offset
|
||||
if (organisationType == SEQUENTIAL)
|
||||
{
|
||||
offset += segment.SegmentDataLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Random organization: segment headers are finished. Data part starts and the offset can be set.
|
||||
DetermineRandomDataOffsets(segments, offset);
|
||||
}
|
||||
|
||||
private bool IsFileHeaderPresent()
|
||||
{
|
||||
subInputStream.Mark();
|
||||
|
||||
foreach (int magicByte in FILE_HEADER_ID)
|
||||
{
|
||||
if (magicByte != subInputStream.Read())
|
||||
{
|
||||
subInputStream.Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
subInputStream.Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the start of the data parts and sets the offset.
|
||||
/// </summary>
|
||||
private void DetermineRandomDataOffsets(List<SegmentHeader> segments, long offset)
|
||||
{
|
||||
if (organisationType == RANDOM)
|
||||
{
|
||||
foreach (SegmentHeader s in segments)
|
||||
{
|
||||
s.SegmentDataStartOffset = offset;
|
||||
offset += s.SegmentDataLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method reads the stream and sets variables for information about organization type and length etc.
|
||||
/// </summary>
|
||||
private void ParseFileHeader()
|
||||
{
|
||||
subInputStream.Seek(0);
|
||||
|
||||
// D.4.1 - ID string, read will be skipped
|
||||
subInputStream.SkipBytes(8);
|
||||
|
||||
// D.4.2 Header flag (1 byte):
|
||||
|
||||
// Bit 3-7 are reserved and must be 0
|
||||
subInputStream.ReadBits(5);
|
||||
|
||||
// Bit 2 - Indicates if extended templates are used
|
||||
if (subInputStream.ReadBit() == 1)
|
||||
{
|
||||
gbUseExtTemplate = true;
|
||||
}
|
||||
|
||||
// Bit 1 - Indicates if amount of pages are unknown
|
||||
if (subInputStream.ReadBit() != 1)
|
||||
{
|
||||
IsNumberOfPageUnknown = false;
|
||||
}
|
||||
|
||||
// Bit 0 - Indicates file organisation type
|
||||
organisationType = (short)subInputStream.ReadBit();
|
||||
|
||||
// D.4.3 Number of pages (field is only present if amount of pages are 'NOT unknown')
|
||||
if (!IsNumberOfPageUnknown)
|
||||
{
|
||||
NumberOfPages = (int)subInputStream.ReadUnsignedInt();
|
||||
fileHeaderLength = 13;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method checks, if the stream is at its end to avoid
|
||||
/// <see cref="EndOfStreamException"/>s and reads 32 bits.
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns>true, if if end of stream reached. false, if there are more bytes to read</returns>
|
||||
private bool ReachedEndOfStream(long offset)
|
||||
{
|
||||
try
|
||||
{
|
||||
subInputStream.Seek(offset);
|
||||
subInputStream.ReadBits(32);
|
||||
return false;
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal Jbig2Globals GetGlobalSegments()
|
||||
{
|
||||
return globalSegments;
|
||||
}
|
||||
|
||||
internal bool IsAmountOfPagesUnknown()
|
||||
{
|
||||
return IsNumberOfPageUnknown;
|
||||
}
|
||||
|
||||
internal bool IsGbUseExtTemplate()
|
||||
{
|
||||
return gbUseExtTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Exception.cs
Normal file
22
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Exception.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class Jbig2Exception : Exception
|
||||
{
|
||||
public Jbig2Exception()
|
||||
{
|
||||
}
|
||||
|
||||
public Jbig2Exception(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public Jbig2Exception(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Globals.cs
Normal file
26
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Globals.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// This class stores segments, that aren't associated to a page.
|
||||
/// If the data is embedded in another format, for example PDF, this segments might be stored separately in the file.
|
||||
/// This segments will be decoded on demand and all results are stored in the document object and can be retrieved from
|
||||
/// there.
|
||||
/// </summary>
|
||||
internal class Jbig2Globals
|
||||
{
|
||||
// This map contains all segments, that are not associated with a page. The key is the segment number.
|
||||
private readonly Dictionary<int, SegmentHeader> globalSegments = new Dictionary<int, SegmentHeader>();
|
||||
|
||||
internal SegmentHeader GetSegment(int segmentNumber)
|
||||
{
|
||||
return globalSegments[segmentNumber];
|
||||
}
|
||||
|
||||
internal void AddSegment(int segmentNumber, SegmentHeader segment)
|
||||
{
|
||||
globalSegments[segmentNumber] = segment;
|
||||
}
|
||||
}
|
||||
}
|
||||
363
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Page.cs
Normal file
363
src/UglyToad.PdfPig/Filters/Jbig2/Jbig2Page.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Util;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a JBIG2 page.
|
||||
/// </summary>
|
||||
internal class Jbig2Page
|
||||
{
|
||||
// This list contains all segments of this page, sorted by segment number in ascending order.
|
||||
private readonly SortedDictionary<int, SegmentHeader> segments = new SortedDictionary<int, SegmentHeader>();
|
||||
|
||||
// NOTE: page number != segmentList index
|
||||
private readonly int pageNumber;
|
||||
|
||||
// The page bitmap that represents the page buffer
|
||||
private Bitmap pageBitmap;
|
||||
|
||||
private int finalHeight;
|
||||
private int finalWidth;
|
||||
private int resolutionX;
|
||||
private int resolutionY;
|
||||
|
||||
private readonly Jbig2Document document;
|
||||
|
||||
internal Jbig2Page(Jbig2Document document, int pageNumber)
|
||||
{
|
||||
this.document = document;
|
||||
this.pageNumber = pageNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method searches for a segment specified by its number.
|
||||
/// </summary>
|
||||
/// <param name="number">Segment number of the segment to search for.</param>
|
||||
/// <returns>The retrieved <see cref="SegmentHeader"/> or null.</returns>
|
||||
internal SegmentHeader GetSegment(int number)
|
||||
{
|
||||
SegmentHeader s = segments.ContainsKey(number) ? segments[number] : null;
|
||||
|
||||
if (null != s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
if (null != document)
|
||||
{
|
||||
return document.GetGlobalSegment(number);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the associated page information segment.
|
||||
/// </summary>
|
||||
/// <returns>The associated <see cref="PageInformation"/> segment or null if not available.</returns>
|
||||
internal SegmentHeader GetPageInformationSegment()
|
||||
{
|
||||
foreach (SegmentHeader s in segments.Values)
|
||||
{
|
||||
if (s.SegmentType == 48)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the decoded bitmap if present.Otherwise the page bitmap will be composed before returning
|
||||
/// the result.
|
||||
/// </summary>
|
||||
/// <returns>The result of decoding a page</returns>
|
||||
/// <exception cref="Jbig2Exception"/>
|
||||
/// <exception cref="System.IO.IOException"/>
|
||||
public Bitmap GetBitmap()
|
||||
{
|
||||
if (null == pageBitmap)
|
||||
{
|
||||
ComposePageBitmap();
|
||||
}
|
||||
return pageBitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method composes the bitmaps of segments to a page and stores the page as a <see cref="Bitmap"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="Jbig2Exception"/>
|
||||
/// <exception cref="System.IO.IOException"/>
|
||||
private void ComposePageBitmap()
|
||||
{
|
||||
if (pageNumber > 0)
|
||||
{
|
||||
// Page 79, 1) Decoding the page information segment
|
||||
PageInformation pageInformation = (PageInformation)GetPageInformationSegment()
|
||||
.GetSegmentData();
|
||||
CreatePage(pageInformation);
|
||||
ClearSegmentData();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePage(PageInformation pageInformation)
|
||||
{
|
||||
if (!pageInformation.IsStriped || pageInformation.BitmapHeight != -1)
|
||||
{
|
||||
// Page 79, 4)
|
||||
CreateNormalPage(pageInformation);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateStripedPage(pageInformation);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateNormalPage(PageInformation pageInformation)
|
||||
{
|
||||
pageBitmap = new Bitmap(pageInformation.BitmapWidth, pageInformation.BitmapHeight);
|
||||
|
||||
// Page 79, 3)
|
||||
// If default pixel value is not 0, byte will be filled with 0xff
|
||||
if (pageInformation.DefaultPixelValue != 0)
|
||||
{
|
||||
ArrayHelper.Fill(pageBitmap.GetByteArray(), (byte)0xff);
|
||||
}
|
||||
|
||||
foreach (SegmentHeader s in segments.Values)
|
||||
{
|
||||
// Page 79, 5)
|
||||
switch (s.SegmentType)
|
||||
{
|
||||
case 6: // Immediate text region
|
||||
case 7: // Immediate lossless text region
|
||||
case 22: // Immediate halftone region
|
||||
case 23: // Immediate lossless halftone region
|
||||
case 38: // Immediate generic region
|
||||
case 39: // Immediate lossless generic region
|
||||
case 42: // Immediate generic refinement region
|
||||
case 43: // Immediate lossless generic refinement region
|
||||
IRegion r = (IRegion)s.GetSegmentData();
|
||||
|
||||
Bitmap regionBitmap = r.GetRegionBitmap();
|
||||
|
||||
if (FitsPage(pageInformation, regionBitmap))
|
||||
{
|
||||
pageBitmap = regionBitmap;
|
||||
}
|
||||
else
|
||||
{
|
||||
RegionSegmentInformation regionInfo = r.RegionInfo;
|
||||
CombinationOperator op = GetCombinationOperator(pageInformation,
|
||||
regionInfo.CombinationOperator);
|
||||
Bitmaps.Blit(regionBitmap, pageBitmap, regionInfo.X,
|
||||
regionInfo.Y, op);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we have only one region that forms the complete page. If the dimension equals the page's dimension set
|
||||
/// the region's bitmap as the page's bitmap. Otherwise we have to blit the smaller region's bitmap into the page's
|
||||
/// bitmap.
|
||||
/// </summary>
|
||||
private bool FitsPage(PageInformation pageInformation, Bitmap regionBitmap)
|
||||
{
|
||||
return CountRegions() == 1 && pageInformation.DefaultPixelValue == 0
|
||||
&& pageInformation.BitmapWidth == regionBitmap.Width
|
||||
&& pageInformation.BitmapHeight == regionBitmap.Height;
|
||||
}
|
||||
|
||||
private void CreateStripedPage(PageInformation pageInformation)
|
||||
{
|
||||
List<ISegmentData> pageStripes = CollectPageStripes();
|
||||
|
||||
pageBitmap = new Bitmap(pageInformation.BitmapWidth, finalHeight);
|
||||
|
||||
int startLine = 0;
|
||||
foreach (ISegmentData sd in pageStripes)
|
||||
{
|
||||
if (sd is EndOfStripe)
|
||||
{
|
||||
startLine = ((EndOfStripe)sd).GetLineNumber() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
IRegion r = (IRegion)sd;
|
||||
RegionSegmentInformation regionInfo = r.RegionInfo;
|
||||
CombinationOperator op = GetCombinationOperator(pageInformation,
|
||||
regionInfo.CombinationOperator);
|
||||
Bitmaps.Blit(r.GetRegionBitmap(), pageBitmap, regionInfo.X, startLine,
|
||||
op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ISegmentData> CollectPageStripes()
|
||||
{
|
||||
List<ISegmentData> pageStripes = new List<ISegmentData>();
|
||||
foreach (SegmentHeader s in segments.Values)
|
||||
{
|
||||
// Page 79, 5)
|
||||
switch (s.SegmentType)
|
||||
{
|
||||
case 6: // Immediate text region
|
||||
case 7: // Immediate lossless text region
|
||||
case 22: // Immediate halftone region
|
||||
case 23: // Immediate lossless halftone region
|
||||
case 38: // Immediate generic region
|
||||
case 39: // Immediate lossless generic region
|
||||
case 42: // Immediate generic refinement region
|
||||
case 43: // Immediate lossless generic refinement region
|
||||
IRegion r = (IRegion)s.GetSegmentData();
|
||||
pageStripes.Add(r);
|
||||
break;
|
||||
|
||||
case 50: // End of stripe
|
||||
EndOfStripe eos = (EndOfStripe)s.GetSegmentData();
|
||||
pageStripes.Add(eos);
|
||||
finalHeight = eos.GetLineNumber() + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pageStripes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method counts the regions segments. If there is only one region, the bitmap
|
||||
/// of this segment is equal to the page bitmap and blitting is not necessary.
|
||||
/// </summary>
|
||||
/// <returns>Number of regions.</returns>
|
||||
private int CountRegions()
|
||||
{
|
||||
int regionCount = 0;
|
||||
|
||||
foreach (SegmentHeader s in segments.Values)
|
||||
{
|
||||
switch (s.SegmentType)
|
||||
{
|
||||
case 6: // Immediate text region
|
||||
case 7: // Immediate lossless text region
|
||||
case 22: // Immediate halftone region
|
||||
case 23: // Immediate lossless halftone region
|
||||
case 38: // Immediate generic region
|
||||
case 39: // Immediate lossless generic region
|
||||
case 42: // Immediate generic refinement region
|
||||
case 43: // Immediate lossless generic refinement region
|
||||
regionCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return regionCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method checks and sets, which combination operator shall be used.
|
||||
/// </summary>
|
||||
/// <param name="pi"><see cref="PageInformation"/> object.</param>
|
||||
/// <param name="newOperator">The combination operator, specified by actual segment.</param>
|
||||
/// <returns>the new combination operator.</returns>
|
||||
private CombinationOperator GetCombinationOperator(PageInformation pi,
|
||||
CombinationOperator newOperator)
|
||||
{
|
||||
if (pi.IsCombinationOperatorOverrideAllowed)
|
||||
{
|
||||
return newOperator;
|
||||
}
|
||||
else
|
||||
{
|
||||
return pi.CombinationOperator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="SegmentHeader"/> into the page's segments map.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to be added.</param>
|
||||
internal void Add(SegmentHeader segment)
|
||||
{
|
||||
segments[segment.SegmentNumber] = segment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the memory-critical segments to force on-demand-decoding and to avoid
|
||||
/// holding the segments' bitmap too long.
|
||||
/// </summary>
|
||||
private void ClearSegmentData()
|
||||
{
|
||||
var keySet = segments.Keys;
|
||||
|
||||
foreach (int key in keySet)
|
||||
{
|
||||
segments[key].CleanSegmentData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset memory-critical parts of page.
|
||||
/// </summary>
|
||||
internal void ClearPageData()
|
||||
{
|
||||
pageBitmap = null;
|
||||
}
|
||||
|
||||
internal int GetHeight()
|
||||
{
|
||||
if (finalHeight == 0)
|
||||
{
|
||||
PageInformation pi = GetPageInformation();
|
||||
if (pi.BitmapHeight == -1)
|
||||
{
|
||||
GetBitmap();
|
||||
}
|
||||
else
|
||||
{
|
||||
finalHeight = pi.BitmapHeight;
|
||||
}
|
||||
}
|
||||
return finalHeight;
|
||||
}
|
||||
|
||||
internal int GetWidth()
|
||||
{
|
||||
if (finalWidth == 0)
|
||||
{
|
||||
finalWidth = GetPageInformation().BitmapWidth;
|
||||
}
|
||||
return finalWidth;
|
||||
}
|
||||
|
||||
internal int GetResolutionX()
|
||||
{
|
||||
if (resolutionX == 0)
|
||||
{
|
||||
resolutionX = GetPageInformation().ResolutionX;
|
||||
}
|
||||
return resolutionX;
|
||||
}
|
||||
|
||||
internal int GetResolutionY()
|
||||
{
|
||||
if (resolutionY == 0)
|
||||
{
|
||||
resolutionY = GetPageInformation().ResolutionY;
|
||||
}
|
||||
return resolutionY;
|
||||
}
|
||||
|
||||
private PageInformation GetPageInformation()
|
||||
{
|
||||
return (PageInformation)GetPageInformationSegment().GetSegmentData();
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
return GetType().Name + " (Page number: " + pageNumber + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
272
src/UglyToad.PdfPig/Filters/Jbig2/MMRConstants.cs
Normal file
272
src/UglyToad.PdfPig/Filters/Jbig2/MMRConstants.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for MMR (de)compression.
|
||||
/// </summary>
|
||||
internal class MMRConstants
|
||||
{
|
||||
public const int COMP_FAXG3 = 0;
|
||||
public const int COMP_FAXG4 = 1;
|
||||
public const int COMP_MMR = 2;
|
||||
public const int COMP_RLE = 3;
|
||||
public const int COMP_FAXG3_2D = 4;
|
||||
|
||||
public const int NOMASK = 0xFFFF;
|
||||
public const int INCOMP = -4;
|
||||
public const int EOF = -3;
|
||||
public const int INVALID = -2;
|
||||
public const int EOL = -1;
|
||||
public const int CODE_P = 0;
|
||||
public const int CODE_H = 1;
|
||||
public const int CODE_V0 = 2;
|
||||
public const int CODE_VR1 = 3;
|
||||
public const int CODE_VR2 = 4;
|
||||
public const int CODE_VR3 = 5;
|
||||
public const int CODE_VL1 = 6;
|
||||
public const int CODE_VL2 = 7;
|
||||
public const int CODE_VL3 = 8;
|
||||
public const int CODE_EXT2D = 9;
|
||||
public const int CODE_EXT1D = 10;
|
||||
public const int CODE_EOL = 11;
|
||||
public const int CODE_EOF = 12;
|
||||
public const int CODE_MAX = 12;
|
||||
|
||||
public static readonly int[][] ModeCodes = new[]{ new []{ 4, 0x1, CODE_P }, // 0001 pass
|
||||
new []{ 3, 0x1, CODE_H }, // 001 horizontal
|
||||
new []{ 1, 0x1, CODE_V0 }, // 1 vert 0
|
||||
new []{ 3, 0x3, CODE_VR1 }, // 011 vert r 1
|
||||
new []{ 6, 0x3, CODE_VR2 }, // 000011 vert r 2
|
||||
new []{ 7, 0x3, CODE_VR3 }, // 0000011 vert r 3
|
||||
new []{ 3, 0x2, CODE_VL1 }, // 010 vert l 1
|
||||
new []{ 6, 0x2, CODE_VL2 }, // 000010 vert l 2
|
||||
new []{ 7, 0x2, CODE_VL3 }, // 0000010 vert l 3
|
||||
new []{ 10, 0xf, CODE_EXT2D }, // 0000001111
|
||||
new []{ 12, 0xf, CODE_EXT1D }, // 000000001111
|
||||
new []{ 12, 0x1, EOL }, // 000000000001
|
||||
};
|
||||
|
||||
public static readonly int[][] WhiteCodes = new[]{ new []{ 4, 0x07, 2 }, // 0111
|
||||
new []{ 4, 0x08, 3 }, // 1000
|
||||
new []{ 4, 0x0B, 4 }, // 1011
|
||||
new []{ 4, 0x0C, 5 }, // 1100
|
||||
new []{ 4, 0x0E, 6 }, // 1110
|
||||
new []{ 4, 0x0F, 7 }, // 1111
|
||||
new []{ 5, 0x12, 128 }, // 1001 0
|
||||
new []{ 5, 0x13, 8 }, // 1001 1
|
||||
new []{ 5, 0x14, 9 }, // 1010 0
|
||||
new []{ 5, 0x1B, 64 }, // 1101 1
|
||||
new []{ 5, 0x07, 10 }, // 0011 1
|
||||
new []{ 5, 0x08, 11 }, // 0100 0
|
||||
new []{ 6, 0x17, 192 }, // 0101 11
|
||||
new []{ 6, 0x18, 1664 }, // 0110 00
|
||||
new []{ 6, 0x2A, 16 }, // 1010 10
|
||||
new []{ 6, 0x2B, 17 }, // 1010 11
|
||||
new []{ 6, 0x03, 13 }, // 0000 11
|
||||
new []{ 6, 0x34, 14 }, // 1101 00
|
||||
new []{ 6, 0x35, 15 }, // 1101 01
|
||||
new []{ 6, 0x07, 1 }, // 0001 11
|
||||
new []{ 6, 0x08, 12 }, // 0010 00
|
||||
new []{ 7, 0x13, 26 }, // 0010 011
|
||||
new []{ 7, 0x17, 21 }, // 0010 111
|
||||
new []{ 7, 0x18, 28 }, // 0011 000
|
||||
new []{ 7, 0x24, 27 }, // 0100 100
|
||||
new []{ 7, 0x27, 18 }, // 0100 111
|
||||
new []{ 7, 0x28, 24 }, // 0101 000
|
||||
new []{ 7, 0x2B, 25 }, // 0101 011
|
||||
new []{ 7, 0x03, 22 }, // 0000 011
|
||||
new []{ 7, 0x37, 256 }, // 0110 111
|
||||
new []{ 7, 0x04, 23 }, // 0000 100
|
||||
new []{ 7, 0x08, 20 }, // 0001 000
|
||||
new []{ 7, 0xC, 19 }, // 0001 100
|
||||
new []{ 8, 0x12, 33 }, // 0001 0010
|
||||
new []{ 8, 0x13, 34 }, // 0001 0011
|
||||
new []{ 8, 0x14, 35 }, // 0001 0100
|
||||
new []{ 8, 0x15, 36 }, // 0001 0101
|
||||
new []{ 8, 0x16, 37 }, // 0001 0110
|
||||
new []{ 8, 0x17, 38 }, // 0001 0111
|
||||
new []{ 8, 0x1A, 31 }, // 0001 1010
|
||||
new []{ 8, 0x1B, 32 }, // 0001 1011
|
||||
new []{ 8, 0x02, 29 }, // 0000 0010
|
||||
new []{ 8, 0x24, 53 }, // 0010 0100
|
||||
new []{ 8, 0x25, 54 }, // 0010 0101
|
||||
new []{ 8, 0x28, 39 }, // 0010 1000
|
||||
new []{ 8, 0x29, 40 }, // 0010 1001
|
||||
new []{ 8, 0x2A, 41 }, // 0010 1010
|
||||
new []{ 8, 0x2B, 42 }, // 0010 1011
|
||||
new []{ 8, 0x2C, 43 }, // 0010 1100
|
||||
new []{ 8, 0x2D, 44 }, // 0010 1101
|
||||
new []{ 8, 0x03, 30 }, // 0000 0011
|
||||
new []{ 8, 0x32, 61 }, // 0011 0010
|
||||
new []{ 8, 0x33, 62 }, // 0011 0011
|
||||
new []{ 8, 0x34, 63 }, // 0011 0100
|
||||
new []{ 8, 0x35, 0 }, // 0011 0101
|
||||
new []{ 8, 0x36, 320 }, // 0011 0110
|
||||
new []{ 8, 0x37, 384 }, // 0011 0111
|
||||
new []{ 8, 0x04, 45 }, // 0000 0100
|
||||
new []{ 8, 0x4A, 59 }, // 0100 1010
|
||||
new []{ 8, 0x4B, 60 }, // 0100 1011
|
||||
new []{ 8, 0x5, 46 }, // 0000 0101
|
||||
new []{ 8, 0x52, 49 }, // 0101 0010
|
||||
new []{ 8, 0x53, 50 }, // 0101 0011
|
||||
new []{ 8, 0x54, 51 }, // 0101 0100
|
||||
new []{ 8, 0x55, 52 }, // 0101 0101
|
||||
new []{ 8, 0x58, 55 }, // 0101 1000
|
||||
new []{ 8, 0x59, 56 }, // 0101 1001
|
||||
new []{ 8, 0x5A, 57 }, // 0101 1010
|
||||
new []{ 8, 0x5B, 58 }, // 0101 1011
|
||||
new []{ 8, 0x64, 448 }, // 0110 0100
|
||||
new []{ 8, 0x65, 512 }, // 0110 0101
|
||||
new []{ 8, 0x67, 640 }, // 0110 0111
|
||||
new []{ 8, 0x68, 576 }, // 0110 1000
|
||||
new []{ 8, 0x0A, 47 }, // 0000 1010
|
||||
new []{ 8, 0x0B, 48 }, // 0000 1011
|
||||
new []{ 9, 0x01, INVALID }, // 0000 0000 1
|
||||
new []{ 9, 0x98, 1472 }, // 0100 1100 0
|
||||
new []{ 9, 0x99, 1536 }, // 0100 1100 1
|
||||
new []{ 9, 0x9A, 1600 }, // 0100 1101 0
|
||||
new []{ 9, 0x9B, 1728 }, // 0100 1101 1
|
||||
new []{ 9, 0xCC, 704 }, // 0110 0110 0
|
||||
new []{ 9, 0xCD, 768 }, // 0110 0110 1
|
||||
new []{ 9, 0xD2, 832 }, // 0110 1001 0
|
||||
new []{ 9, 0xD3, 896 }, // 0110 1001 1
|
||||
new []{ 9, 0xD4, 960 }, // 0110 1010 0
|
||||
new []{ 9, 0xD5, 1024 }, // 0110 1010 1
|
||||
new []{ 9, 0xD6, 1088 }, // 0110 1011 0
|
||||
new []{ 9, 0xD7, 1152 }, // 0110 1011 1
|
||||
new []{ 9, 0xD8, 1216 }, // 0110 1100 0
|
||||
new []{ 9, 0xD9, 1280 }, // 0110 1100 1
|
||||
new []{ 9, 0xDA, 1344 }, // 0110 1101 0
|
||||
new []{ 9, 0xDB, 1408 }, // 0110 1101 1
|
||||
new []{ 10, 0x01, INVALID }, // 0000 0000 01
|
||||
new []{ 11, 0x01, INVALID }, // 0000 0000 001
|
||||
new []{ 11, 0x08, 1792 }, // 0000 0001 000
|
||||
new []{ 11, 0x0C, 1856 }, // 0000 0001 100
|
||||
new []{ 11, 0x0D, 1920 }, // 0000 0001 101
|
||||
new []{ 12, 0x00, EOF }, // 0000 0000 0000
|
||||
new []{ 12, 0x01, EOL }, // 0000 0000 0001
|
||||
new []{ 12, 0x12, 1984 }, // 0000 0001 0010
|
||||
new []{ 12, 0x13, 2048 }, // 0000 0001 0011
|
||||
new []{ 12, 0x14, 2112 }, // 0000 0001 0100
|
||||
new []{ 12, 0x15, 2176 }, // 0000 0001 0101
|
||||
new []{ 12, 0x16, 2240 }, // 0000 0001 0110
|
||||
new []{ 12, 0x17, 2304 }, // 0000 0001 0111
|
||||
new []{ 12, 0x1C, 2368 }, // 0000 0001 1100
|
||||
new []{ 12, 0x1D, 2432 }, // 0000 0001 1101
|
||||
new []{ 12, 0x1E, 2496 }, // 0000 0001 1110
|
||||
new []{ 12, 0x1F, 2560 }, // 0000 0001 1111
|
||||
};
|
||||
public static readonly int MAX_WHITE_RUN = 2560;
|
||||
|
||||
public static readonly int[][] BlackCodes = new[]{ new []{ 2, 0x02, 3 }, // 10
|
||||
new []{ 2, 0x03, 2 }, // 11
|
||||
new []{ 3, 0x02, 1 }, // 010
|
||||
new []{ 3, 0x03, 4 }, // 011
|
||||
new []{ 4, 0x02, 6 }, // 0010
|
||||
new []{ 4, 0x03, 5 }, // 0011
|
||||
new []{ 5, 0x03, 7 }, // 0001 1
|
||||
new []{ 6, 0x04, 9 }, // 0001 00
|
||||
new []{ 6, 0x05, 8 }, // 0001 01
|
||||
new []{ 7, 0x04, 10 }, // 0000 100
|
||||
new []{ 7, 0x05, 11 }, // 0000 101
|
||||
new []{ 7, 0x07, 12 }, // 0000 111
|
||||
new []{ 8, 0x04, 13 }, // 0000 0100
|
||||
new []{ 8, 0x07, 14 }, // 0000 0111
|
||||
new []{ 9, 0x01, INVALID }, // 0000 0000 1
|
||||
new []{ 9, 0x18, 15 }, // 0000 1100 0
|
||||
new []{ 10, 0x01, INVALID }, // 0000 0000 01
|
||||
new []{ 10, 0x17, 16 }, // 0000 0101 11
|
||||
new []{ 10, 0x18, 17 }, // 0000 0110 00
|
||||
new []{ 10, 0x37, 0 }, // 0000 1101 11
|
||||
new []{ 10, 0x08, 18 }, // 0000 0010 00
|
||||
new []{ 10, 0x0F, 64 }, // 0000 0011 11
|
||||
new []{ 11, 0x01, INVALID }, // 0000 0000 001
|
||||
new []{ 11, 0x17, 24 }, // 0000 0010 111
|
||||
new []{ 11, 0x18, 25 }, // 0000 0011 000
|
||||
new []{ 11, 0x28, 23 }, // 0000 0101 000
|
||||
new []{ 11, 0x37, 22 }, // 0000 0110 111
|
||||
new []{ 11, 0x67, 19 }, // 0000 1100 111
|
||||
new []{ 11, 0x68, 20 }, // 0000 1101 000
|
||||
new []{ 11, 0x6C, 21 }, // 0000 1101 100
|
||||
new []{ 11, 0x08, 1792 }, // 0000 0001 000
|
||||
new []{ 11, 0x0C, 1856 }, // 0000 0001 100
|
||||
new []{ 11, 0x0D, 1920 }, // 0000 0001 101
|
||||
new []{ 12, 0x00, EOF }, // 0000 0000 0000
|
||||
new []{ 12, 0x01, EOL }, // 0000 0000 0001
|
||||
new []{ 12, 0x12, 1984 }, // 0000 0001 0010
|
||||
new []{ 12, 0x13, 2048 }, // 0000 0001 0011
|
||||
new []{ 12, 0x14, 2112 }, // 0000 0001 0100
|
||||
new []{ 12, 0x15, 2176 }, // 0000 0001 0101
|
||||
new []{ 12, 0x16, 2240 }, // 0000 0001 0110
|
||||
new []{ 12, 0x17, 2304 }, // 0000 0001 0111
|
||||
new []{ 12, 0x1C, 2368 }, // 0000 0001 1100
|
||||
new []{ 12, 0x1D, 2432 }, // 0000 0001 1101
|
||||
new []{ 12, 0x1E, 2496 }, // 0000 0001 1110
|
||||
new []{ 12, 0x1F, 2560 }, // 0000 0001 1111
|
||||
new []{ 12, 0x24, 52 }, // 0000 0010 0100
|
||||
new []{ 12, 0x27, 55 }, // 0000 0010 0111
|
||||
new []{ 12, 0x28, 56 }, // 0000 0010 1000
|
||||
new []{ 12, 0x2B, 59 }, // 0000 0010 1011
|
||||
new []{ 12, 0x2C, 60 }, // 0000 0010 1100
|
||||
new []{ 12, 0x33, 320 }, // 0000 0011 0011
|
||||
new []{ 12, 0x34, 384 }, // 0000 0011 0100
|
||||
new []{ 12, 0x35, 448 }, // 0000 0011 0101
|
||||
new []{ 12, 0x37, 53 }, // 0000 0011 0111
|
||||
new []{ 12, 0x38, 54 }, // 0000 0011 1000
|
||||
new []{ 12, 0x52, 50 }, // 0000 0101 0010
|
||||
new []{ 12, 0x53, 51 }, // 0000 0101 0011
|
||||
new []{ 12, 0x54, 44 }, // 0000 0101 0100
|
||||
new []{ 12, 0x55, 45 }, // 0000 0101 0101
|
||||
new []{ 12, 0x56, 46 }, // 0000 0101 0110
|
||||
new []{ 12, 0x57, 47 }, // 0000 0101 0111
|
||||
new []{ 12, 0x58, 57 }, // 0000 0101 1000
|
||||
new []{ 12, 0x59, 58 }, // 0000 0101 1001
|
||||
new []{ 12, 0x5A, 61 }, // 0000 0101 1010
|
||||
new []{ 12, 0x5B, 256 }, // 0000 0101 1011
|
||||
new []{ 12, 0x64, 48 }, // 0000 0110 0100
|
||||
new []{ 12, 0x65, 49 }, // 0000 0110 0101
|
||||
new []{ 12, 0x66, 62 }, // 0000 0110 0110
|
||||
new []{ 12, 0x67, 63 }, // 0000 0110 0111
|
||||
new []{ 12, 0x68, 30 }, // 0000 0110 1000
|
||||
new []{ 12, 0x69, 31 }, // 0000 0110 1001
|
||||
new []{ 12, 0x6A, 32 }, // 0000 0110 1010
|
||||
new []{ 12, 0x6B, 33 }, // 0000 0110 1011
|
||||
new []{ 12, 0x6C, 40 }, // 0000 0110 1100
|
||||
new []{ 12, 0x6D, 41 }, // 0000 0110 1101
|
||||
new []{ 12, 0xC8, 128 }, // 0000 1100 1000
|
||||
new []{ 12, 0xC9, 192 }, // 0000 1100 1001
|
||||
new []{ 12, 0xCA, 26 }, // 0000 1100 1010
|
||||
new []{ 12, 0xCB, 27 }, // 0000 1100 1011
|
||||
new []{ 12, 0xCC, 28 }, // 0000 1100 1100
|
||||
new []{ 12, 0xCD, 29 }, // 0000 1100 1101
|
||||
new []{ 12, 0xD2, 34 }, // 0000 1101 0010
|
||||
new []{ 12, 0xD3, 35 }, // 0000 1101 0011
|
||||
new []{ 12, 0xD4, 36 }, // 0000 1101 0100
|
||||
new []{ 12, 0xD5, 37 }, // 0000 1101 0101
|
||||
new []{ 12, 0xD6, 38 }, // 0000 1101 0110
|
||||
new []{ 12, 0xD7, 39 }, // 0000 1101 0111
|
||||
new []{ 12, 0xDA, 42 }, // 0000 1101 1010
|
||||
new []{ 12, 0xDB, 43 }, // 0000 1101 1011
|
||||
new []{ 13, 0x4A, 640 }, // 0000 0010 0101 0
|
||||
new []{ 13, 0x4B, 704 }, // 0000 0010 0101 1
|
||||
new []{ 13, 0x4C, 768 }, // 0000 0010 0110 0
|
||||
new []{ 13, 0x4D, 832 }, // 0000 0010 0110 1
|
||||
new []{ 13, 0x52, 1280 }, // 0000 0010 1001 0
|
||||
new []{ 13, 0x53, 1344 }, // 0000 0010 1001 1
|
||||
new []{ 13, 0x54, 1408 }, // 0000 0010 1010 0
|
||||
new []{ 13, 0x55, 1472 }, // 0000 0010 1010 1
|
||||
new []{ 13, 0x5A, 1536 }, // 0000 0010 1101 0
|
||||
new []{ 13, 0x5B, 1600 }, // 0000 0010 1101 1
|
||||
new []{ 13, 0x64, 1664 }, // 0000 0011 0010 0
|
||||
new []{ 13, 0x65, 1728 }, // 0000 0011 0010 1
|
||||
new []{ 13, 0x6C, 512 }, // 0000 0011 0110 0
|
||||
new []{ 13, 0x6D, 576 }, // 0000 0011 0110 1
|
||||
new []{ 13, 0x72, 896 }, // 0000 0011 1001 0
|
||||
new []{ 13, 0x73, 960 }, // 0000 0011 1001 1
|
||||
new []{ 13, 0x74, 1024 }, // 0000 0011 1010 0
|
||||
new []{ 13, 0x75, 1088 }, // 0000 0011 1010 1
|
||||
new []{ 13, 0x76, 1152 }, // 0000 0011 1011 0
|
||||
new []{ 13, 0x77, 1216 }, // 0000 0011 1011 1
|
||||
};
|
||||
public static readonly int MAX_BLACK_RUN = 2560;
|
||||
}
|
||||
}
|
||||
705
src/UglyToad.PdfPig/Filters/Jbig2/MMRDecompressor.cs
Normal file
705
src/UglyToad.PdfPig/Filters/Jbig2/MMRDecompressor.cs
Normal file
@@ -0,0 +1,705 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig.Util;
|
||||
|
||||
/// <summary>
|
||||
/// A decompressor for MMR compression.
|
||||
/// </summary>
|
||||
internal class MMRDecompressor
|
||||
{
|
||||
private readonly int width;
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// A class encapsulating the compressed raw data.
|
||||
/// </summary>
|
||||
private class RunData
|
||||
{
|
||||
private static readonly int MAX_RUN_DATA_BUFFER = 1024 << 7; // 1024 * 128
|
||||
private static readonly int MIN_RUN_DATA_BUFFER = 3; // min. bytes to decompress
|
||||
private static readonly int CODE_OFFSET = 24;
|
||||
|
||||
// Compressed data stream.
|
||||
private readonly IImageInputStream stream;
|
||||
private readonly byte[] buffer;
|
||||
|
||||
private int bufferBase;
|
||||
private int bufferTop;
|
||||
|
||||
private int lastOffset = 0;
|
||||
private int lastCode = 0;
|
||||
|
||||
internal int Offset { get; set; }
|
||||
|
||||
internal RunData(IImageInputStream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
Offset = 0;
|
||||
lastOffset = 1;
|
||||
|
||||
try
|
||||
{
|
||||
long length = stream.Length;
|
||||
length = Math.Min(Math.Max(MIN_RUN_DATA_BUFFER, length), MAX_RUN_DATA_BUFFER);
|
||||
buffer = new byte[(int)length];
|
||||
FillBuffer(0);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
buffer = new byte[10];
|
||||
}
|
||||
}
|
||||
|
||||
internal Code UncompressGetCode(Code[] table)
|
||||
{
|
||||
return UncompressGetCodeLittleEndian(table);
|
||||
}
|
||||
|
||||
internal Code UncompressGetCodeLittleEndian(Code[] table)
|
||||
{
|
||||
int code = UncompressGetNextCodeLittleEndian() & 0xffffff;
|
||||
Code result = table[code >> CODE_OFFSET - FIRST_LEVEL_TABLE_SIZE];
|
||||
|
||||
// perform second-level lookup
|
||||
if (null != result && null != result.SubTable)
|
||||
{
|
||||
result = result.SubTable[(code >> CODE_OFFSET - FIRST_LEVEL_TABLE_SIZE
|
||||
- SECOND_LEVEL_TABLE_SIZE) & SECOND_LEVEL_TABLE_MASK];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill up the code word in little endian mode. This is a hotspot, therefore the algorithm is heavily optimised.
|
||||
/// For the frequent cases (i.e. short words) we try to get away with as little work as possible.
|
||||
/// This method returns code words of 16 bits, which are aligned to the 24th bit.The lowest 8 bits are used as a
|
||||
/// "queue" of bits so that an access to the actual data is only needed, when this queue becomes empty.
|
||||
/// </summary>
|
||||
private int UncompressGetNextCodeLittleEndian()
|
||||
{
|
||||
try
|
||||
{
|
||||
// the number of bits to fill (offset difference)
|
||||
int bitsToFill = Offset - lastOffset;
|
||||
|
||||
// check whether we can refill, or need to fill in absolute mode
|
||||
if (bitsToFill < 0 || bitsToFill > 24)
|
||||
{
|
||||
// refill at absolute offset
|
||||
int byteOffset = (Offset >> 3) - bufferBase; // offset >> 3 is equivalent to offset / 8
|
||||
|
||||
if (byteOffset >= bufferTop)
|
||||
{
|
||||
byteOffset += bufferBase;
|
||||
FillBuffer(byteOffset);
|
||||
byteOffset -= bufferBase;
|
||||
}
|
||||
|
||||
lastCode = (buffer[byteOffset] & 0xff) << 16
|
||||
| (buffer[byteOffset + 1] & 0xff) << 8
|
||||
| (buffer[byteOffset + 2] & 0xff);
|
||||
|
||||
int bitOffset = Offset & 7; // equivalent to offset % 8
|
||||
lastCode <<= bitOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the offset to the next byte boundary as seen from the last offset
|
||||
int bitOffset = lastOffset & 7;
|
||||
int avail = 7 - bitOffset;
|
||||
|
||||
// check whether there are enough bits in the "queue"
|
||||
if (bitsToFill <= avail)
|
||||
{
|
||||
lastCode <<= bitsToFill;
|
||||
}
|
||||
else
|
||||
{
|
||||
int byteOffset = (lastOffset >> 3) + 3 - bufferBase;
|
||||
|
||||
if (byteOffset >= bufferTop)
|
||||
{
|
||||
byteOffset += bufferBase;
|
||||
FillBuffer(byteOffset);
|
||||
byteOffset -= bufferBase;
|
||||
}
|
||||
|
||||
bitOffset = 8 - bitOffset;
|
||||
do
|
||||
{
|
||||
lastCode <<= bitOffset;
|
||||
lastCode |= buffer[byteOffset] & 0xff;
|
||||
bitsToFill -= bitOffset;
|
||||
byteOffset++;
|
||||
bitOffset = 8;
|
||||
} while (bitsToFill >= 8);
|
||||
|
||||
lastCode <<= bitsToFill; // shift the rest
|
||||
}
|
||||
}
|
||||
lastOffset = Offset;
|
||||
|
||||
return lastCode;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IndexOutOfRangeException(
|
||||
"Corrupted RLE data caused by an IOException while reading raw data: "
|
||||
+ e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void FillBuffer(int byteOffset)
|
||||
{
|
||||
bufferBase = byteOffset;
|
||||
lock (stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.Seek(byteOffset);
|
||||
bufferTop = stream.Read(buffer);
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
// you never know which kind of EOF will kick in
|
||||
bufferTop = -1;
|
||||
}
|
||||
// check filling degree
|
||||
if (bufferTop > -1 && bufferTop < 3)
|
||||
{
|
||||
// CK: if filling degree is too small,
|
||||
// smoothly fill up to the next three bytes or substitute with with
|
||||
// empty bytes
|
||||
int read = 0;
|
||||
while (bufferTop < 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
read = stream.Read();
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
read = -1;
|
||||
}
|
||||
buffer[bufferTop++] = read == -1 ? (byte)0 : (byte)(read & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
// leave some room, in order to save a few tests in the calling code
|
||||
bufferTop -= 3;
|
||||
|
||||
if (bufferTop < 0)
|
||||
{
|
||||
|
||||
// if we're at EOF, just supply zero-bytes
|
||||
ArrayHelper.Fill(buffer, (byte)0);
|
||||
bufferTop = buffer.Length - 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip to next byte
|
||||
/// </summary>
|
||||
internal void Align()
|
||||
{
|
||||
Offset = ((Offset + 7) >> 3) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
private class Code
|
||||
{
|
||||
internal Code[] SubTable { get; set; }
|
||||
|
||||
internal int BitLength { get; }
|
||||
internal int CodeWord { get; }
|
||||
internal int RunLength { get; }
|
||||
|
||||
internal Code(int[] codeData)
|
||||
{
|
||||
BitLength = codeData[0];
|
||||
CodeWord = codeData[1];
|
||||
RunLength = codeData[2];
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
return BitLength + "/" + CodeWord + "/" + RunLength;
|
||||
}
|
||||
|
||||
public override sealed bool Equals(object obj)
|
||||
{
|
||||
return (obj is Code) &&
|
||||
((Code)obj).BitLength == BitLength &&
|
||||
((Code)obj).CodeWord == CodeWord &&
|
||||
((Code)obj).RunLength == RunLength;
|
||||
}
|
||||
|
||||
public override sealed int GetHashCode()
|
||||
{
|
||||
return (BitLength, CodeWord, RunLength).GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly int FIRST_LEVEL_TABLE_SIZE = 8;
|
||||
private static readonly int FIRST_LEVEL_TABLE_MASK = (1 << FIRST_LEVEL_TABLE_SIZE) - 1;
|
||||
private static readonly int SECOND_LEVEL_TABLE_SIZE = 5;
|
||||
private static readonly int SECOND_LEVEL_TABLE_MASK = (1 << SECOND_LEVEL_TABLE_SIZE) - 1;
|
||||
|
||||
private static Code[] WhiteTable = null;
|
||||
private static Code[] BlackTable = null;
|
||||
private static Code[] ModeTable = null;
|
||||
|
||||
private readonly RunData data;
|
||||
|
||||
private static void InitTables()
|
||||
{
|
||||
if (null == WhiteTable)
|
||||
{
|
||||
WhiteTable = CreateLittleEndianTable(MMRConstants.WhiteCodes);
|
||||
BlackTable = CreateLittleEndianTable(MMRConstants.BlackCodes);
|
||||
ModeTable = CreateLittleEndianTable(MMRConstants.ModeCodes);
|
||||
}
|
||||
}
|
||||
|
||||
private static int Uncompress2D(RunData runData, int[] referenceOffsets, int refRunLength,
|
||||
int[] runOffsets, int width)
|
||||
{
|
||||
|
||||
int referenceBufferOffset = 0;
|
||||
int currentBufferOffset = 0;
|
||||
int currentLineBitPosition = 0;
|
||||
|
||||
bool whiteRun = true; // Always start with a white run
|
||||
Code code = null; // Storage var for current code being processed
|
||||
|
||||
referenceOffsets[refRunLength] = referenceOffsets[refRunLength + 1] = width;
|
||||
referenceOffsets[refRunLength + 2] = referenceOffsets[refRunLength + 3] = width + 1;
|
||||
|
||||
try
|
||||
{
|
||||
decodeLoop: while (currentLineBitPosition < width)
|
||||
{
|
||||
// Get the mode code
|
||||
code = runData.UncompressGetCode(ModeTable);
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
runData.Offset++;
|
||||
goto endDecodeLoop;
|
||||
}
|
||||
|
||||
// Add the code length to the bit offset
|
||||
runData.Offset += code.BitLength;
|
||||
|
||||
switch (code.RunLength)
|
||||
{
|
||||
case MMRConstants.CODE_V0:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset];
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_VR1:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 1;
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_VL1:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 1;
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_H:
|
||||
for (int ever = 1; ever > 0;)
|
||||
{
|
||||
code = runData.UncompressGetCode(whiteRun == true ? WhiteTable : BlackTable);
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
goto endDecodeLoop;
|
||||
}
|
||||
|
||||
runData.Offset += code.BitLength;
|
||||
if (code.RunLength < 64)
|
||||
{
|
||||
if (code.RunLength < 0)
|
||||
{
|
||||
runOffsets[currentBufferOffset++] = currentLineBitPosition;
|
||||
code = null;
|
||||
goto endDecodeLoop;
|
||||
|
||||
}
|
||||
currentLineBitPosition += code.RunLength;
|
||||
runOffsets[currentBufferOffset++] = currentLineBitPosition;
|
||||
break;
|
||||
}
|
||||
currentLineBitPosition += code.RunLength;
|
||||
}
|
||||
|
||||
int firstHalfBitPos = currentLineBitPosition;
|
||||
for (int ever1 = 1; ever1 > 0;)
|
||||
{
|
||||
code = runData.UncompressGetCode(whiteRun != true ? WhiteTable : BlackTable);
|
||||
if (code == null)
|
||||
{
|
||||
goto endDecodeLoop;
|
||||
}
|
||||
|
||||
runData.Offset += code.BitLength;
|
||||
if (code.RunLength < 64)
|
||||
{
|
||||
if (code.RunLength < 0)
|
||||
{
|
||||
runOffsets[currentBufferOffset++] = currentLineBitPosition;
|
||||
goto endDecodeLoop;
|
||||
}
|
||||
currentLineBitPosition += code.RunLength;
|
||||
// don't generate 0-length run at EOL for cases where the line ends in an H-run.
|
||||
if (currentLineBitPosition < width
|
||||
|| currentLineBitPosition != firstHalfBitPos)
|
||||
{
|
||||
runOffsets[currentBufferOffset++] = currentLineBitPosition;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
currentLineBitPosition += code.RunLength;
|
||||
}
|
||||
|
||||
while (currentLineBitPosition < width
|
||||
&& referenceOffsets[referenceBufferOffset] <= currentLineBitPosition)
|
||||
{
|
||||
referenceBufferOffset += 2;
|
||||
}
|
||||
|
||||
goto decodeLoop;
|
||||
|
||||
case MMRConstants.CODE_P:
|
||||
referenceBufferOffset++;
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset++];
|
||||
goto decodeLoop;
|
||||
|
||||
case MMRConstants.CODE_VR2:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 2;
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_VL2:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 2;
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_VR3:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 3;
|
||||
break;
|
||||
|
||||
case MMRConstants.CODE_VL3:
|
||||
currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 3;
|
||||
break;
|
||||
|
||||
case MMRConstants.EOL:
|
||||
default:
|
||||
// Possibly MMR-decoded
|
||||
if (runData.Offset == 12 && code.RunLength == MMRConstants.EOL)
|
||||
{
|
||||
runData.Offset = 0;
|
||||
Uncompress1D(runData, referenceOffsets, width);
|
||||
runData.Offset++;
|
||||
Uncompress1D(runData, runOffsets, width);
|
||||
int retCode = Uncompress1D(runData, referenceOffsets, width);
|
||||
runData.Offset++;
|
||||
return retCode;
|
||||
}
|
||||
currentLineBitPosition = width;
|
||||
goto decodeLoop;
|
||||
}
|
||||
|
||||
// Only vertical modes get this far
|
||||
if (currentLineBitPosition <= width)
|
||||
{
|
||||
whiteRun = !whiteRun;
|
||||
|
||||
runOffsets[currentBufferOffset++] = currentLineBitPosition;
|
||||
|
||||
if (referenceBufferOffset > 0)
|
||||
{
|
||||
referenceBufferOffset--;
|
||||
}
|
||||
else
|
||||
{
|
||||
referenceBufferOffset++;
|
||||
}
|
||||
|
||||
while (currentLineBitPosition < width
|
||||
&& referenceOffsets[referenceBufferOffset] <= currentLineBitPosition)
|
||||
{
|
||||
referenceBufferOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var strBuf = new StringBuilder();
|
||||
strBuf.Append("whiteRun = ");
|
||||
strBuf.Append(whiteRun);
|
||||
strBuf.Append("\n");
|
||||
strBuf.Append("code = ");
|
||||
strBuf.Append(code);
|
||||
strBuf.Append("\n");
|
||||
strBuf.Append("refOffset = ");
|
||||
strBuf.Append(referenceBufferOffset);
|
||||
strBuf.Append("\n");
|
||||
strBuf.Append("curOffset = ");
|
||||
strBuf.Append(currentBufferOffset);
|
||||
strBuf.Append("\n");
|
||||
strBuf.Append("bitPos = ");
|
||||
strBuf.Append(currentLineBitPosition);
|
||||
strBuf.Append("\n");
|
||||
strBuf.Append("runData.offset = ");
|
||||
strBuf.Append(runData.Offset);
|
||||
strBuf.Append(" ( byte:");
|
||||
strBuf.Append(runData.Offset / 8);
|
||||
strBuf.Append(", bit:");
|
||||
strBuf.Append(runData.Offset & 0x07);
|
||||
strBuf.Append(" )");
|
||||
|
||||
return MMRConstants.EOF;
|
||||
}
|
||||
|
||||
endDecodeLoop:
|
||||
|
||||
if (runOffsets[currentBufferOffset] != width)
|
||||
{
|
||||
runOffsets[currentBufferOffset] = width;
|
||||
}
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
return MMRConstants.EOL;
|
||||
}
|
||||
return currentBufferOffset;
|
||||
}
|
||||
|
||||
public MMRDecompressor(int width, int height, IImageInputStream stream)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
data = new RunData(stream);
|
||||
|
||||
InitTables();
|
||||
}
|
||||
|
||||
public Bitmap Uncompress()
|
||||
{
|
||||
Bitmap result = new Bitmap(width, height);
|
||||
|
||||
int[] currentOffsets = new int[width + 5];
|
||||
int[] referenceOffsets = new int[width + 5];
|
||||
referenceOffsets[0] = width;
|
||||
int refRunLength = 1;
|
||||
|
||||
int count;
|
||||
|
||||
for (int line = 0; line < height; line++)
|
||||
{
|
||||
count = Uncompress2D(data, referenceOffsets, refRunLength, currentOffsets, width);
|
||||
|
||||
if (count == MMRConstants.EOF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
FillBitmap(result, line, currentOffsets, count);
|
||||
}
|
||||
|
||||
// Swap lines
|
||||
int[] tempOffsets = referenceOffsets;
|
||||
referenceOffsets = currentOffsets;
|
||||
currentOffsets = tempOffsets;
|
||||
refRunLength = count;
|
||||
}
|
||||
|
||||
DetectAndSkipEOL();
|
||||
|
||||
data.Align();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DetectAndSkipEOL()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Code code = data.UncompressGetCode(ModeTable);
|
||||
if (null != code && code.RunLength == MMRConstants.EOL)
|
||||
{
|
||||
data.Offset += code.BitLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FillBitmap(Bitmap result, int line, int[] currentOffsets, int count)
|
||||
{
|
||||
int x = 0;
|
||||
int targetByte = result.GetByteIndex(0, line);
|
||||
byte targetByteValue = 0;
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
|
||||
int offset = currentOffsets[index];
|
||||
byte value;
|
||||
|
||||
if ((index & 1) == 0)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = 1;
|
||||
}
|
||||
|
||||
while (x < offset)
|
||||
{
|
||||
targetByteValue = (byte)((targetByteValue << 1) | value);
|
||||
x++;
|
||||
|
||||
if ((x & 7) == 0)
|
||||
{
|
||||
result.SetByte(targetByte++, targetByteValue);
|
||||
targetByteValue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((x & 7) != 0)
|
||||
{
|
||||
targetByteValue <<= 8 - (x & 7);
|
||||
result.SetByte(targetByte, targetByteValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static int Uncompress1D(RunData runData, int[] runOffsets, int width)
|
||||
{
|
||||
bool whiteRun = true;
|
||||
int iBitPos = 0;
|
||||
Code code = null;
|
||||
int refOffset = 0;
|
||||
|
||||
while (iBitPos < width)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (whiteRun)
|
||||
{
|
||||
code = runData.UncompressGetCode(WhiteTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
code = runData.UncompressGetCode(BlackTable);
|
||||
}
|
||||
|
||||
runData.Offset += code.BitLength;
|
||||
|
||||
if (code.RunLength < 0)
|
||||
{
|
||||
goto endloop;
|
||||
}
|
||||
|
||||
iBitPos += code.RunLength;
|
||||
|
||||
if (code.RunLength < 64)
|
||||
{
|
||||
whiteRun = !whiteRun;
|
||||
runOffsets[refOffset++] = iBitPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endloop:
|
||||
|
||||
if (runOffsets[refOffset] != width)
|
||||
{
|
||||
runOffsets[refOffset] = width;
|
||||
}
|
||||
|
||||
return code != null && code.RunLength != MMRConstants.EOL ? refOffset : MMRConstants.EOL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For little endian, the tables are structured like this:
|
||||
///
|
||||
/// v--------v length = FIRST_LEVEL_TABLE_LENGTH
|
||||
/// v---- - v length = SECOND_LEVEL_TABLE_LENGTH
|
||||
/// A code word which fits into the first level table(length= 3)
|
||||
/// [Cccvvvvv]
|
||||
///
|
||||
/// A code word which needs the second level table also(length= 10)
|
||||
/// [Cccccccc] -> [ccvvv]
|
||||
///
|
||||
///
|
||||
/// "C" denotes the first code word bit
|
||||
/// "c" denotes a code word bit
|
||||
/// "v" denotes a variant bit
|
||||
/// </summary>
|
||||
private static Code[] CreateLittleEndianTable(int[][] codes)
|
||||
{
|
||||
var firstLevelTable = new Code[FIRST_LEVEL_TABLE_MASK + 1];
|
||||
for (int i = 0; i < codes.Length; i++)
|
||||
{
|
||||
var code = new Code(codes[i]);
|
||||
|
||||
if (code.BitLength <= FIRST_LEVEL_TABLE_SIZE)
|
||||
{
|
||||
int variantLength = FIRST_LEVEL_TABLE_SIZE - code.BitLength;
|
||||
int baseWord = code.CodeWord << variantLength;
|
||||
|
||||
for (int variant = (1 << variantLength) - 1; variant >= 0; variant--)
|
||||
{
|
||||
int index = baseWord | variant;
|
||||
firstLevelTable[index] = code;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// init second level table
|
||||
int firstLevelIndex = (int)((uint)code.CodeWord >> code.BitLength
|
||||
- FIRST_LEVEL_TABLE_SIZE);
|
||||
|
||||
if (firstLevelTable[firstLevelIndex] == null)
|
||||
{
|
||||
var firstLevelCode = new Code(new int[3]);
|
||||
firstLevelCode.SubTable = new Code[SECOND_LEVEL_TABLE_MASK + 1];
|
||||
firstLevelTable[firstLevelIndex] = firstLevelCode;
|
||||
}
|
||||
|
||||
// fill second level table
|
||||
if (code.BitLength <= FIRST_LEVEL_TABLE_SIZE + SECOND_LEVEL_TABLE_SIZE)
|
||||
{
|
||||
Code[] secondLevelTable = firstLevelTable[firstLevelIndex].SubTable;
|
||||
int variantLength = FIRST_LEVEL_TABLE_SIZE + SECOND_LEVEL_TABLE_SIZE
|
||||
- code.BitLength;
|
||||
int baseWord = (code.CodeWord << variantLength) & SECOND_LEVEL_TABLE_MASK;
|
||||
|
||||
for (int variant = (1 << variantLength) - 1; variant >= 0; variant--)
|
||||
{
|
||||
secondLevelTable[baseWord | variant] = code;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Code table overflow in MMRDecompressor");
|
||||
}
|
||||
}
|
||||
}
|
||||
return firstLevelTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/UglyToad.PdfPig/Filters/Jbig2/Node.cs
Normal file
10
src/UglyToad.PdfPig/Filters/Jbig2/Node.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all nodes in a Huffman tree.
|
||||
/// </summary>
|
||||
internal abstract class Node
|
||||
{
|
||||
public abstract long Decode(IImageInputStream iis);
|
||||
}
|
||||
}
|
||||
21
src/UglyToad.PdfPig/Filters/Jbig2/OutOfBandNode.cs
Normal file
21
src/UglyToad.PdfPig/Filters/Jbig2/OutOfBandNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace UglyToad.PdfPig.Filters.Jbig2
|
||||
{
|
||||
using static HuffmanTable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a out of band node in a Huffman tree.
|
||||
/// </summary>
|
||||
internal class OutOfBandNode : Node
|
||||
{
|
||||
public OutOfBandNode(Code c)
|
||||
{
|
||||
}
|
||||
|
||||
public override sealed long Decode(IImageInputStream iis)
|
||||
{
|
||||
return long.MaxValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user