From b069de0caa6dc8260ebd0aad1d26767f4bab7f08 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 13 Apr 2018 16:35:50 -0700 Subject: [PATCH] Use Leptonica to rewrite all PNGs with predictor Leptonica does a better job of encoding them than Ghostscript, about -15%. For a test file 450k worth of PNGs was reduced to 388k with no loss of quality. --- src/ocrmypdf/leptonica.py | 12 ++++---- src/ocrmypdf/lib/_leptonica.py | 9 +++--- src/ocrmypdf/lib/compile_leptonica.py | 19 +++++++++++++ src/ocrmypdf/pipeline.py | 40 +++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index e28979cf..c7231698 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -108,13 +108,6 @@ class LeptonicaIOError(LeptonicaError): pass -class RemoveColormap(Enum): - to_binary = 0 - to_grayscale = 1 - to_full_color = 2 - based_on_src = 3 - - class Pix: """Wrapper around leptonica's PIX object. @@ -504,6 +497,11 @@ class CompressedData: buf = ffi.buffer(self._compdata.datacomp, self._compdata.nbytescomp) return bytes(buf) + def __getattr__(self, name): + if hasattr(self._compdata, name): + return getattr(self._compdata, name) + raise AttributeError(name) + @staticmethod def _destroy(compdata): lept.lept_free(compdata) diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index 6e1c8d49..0578ec47 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,8 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x0F\x0D\x00\x00\xC4\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC7\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\xC1\x03\x00\x00\x0F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x02\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x08\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xD0\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xC6\x0D\x00\x00\x00\x0F\x00\x00\x49\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x1F\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x49\x11\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC2\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x01\x03\x00\x00\xA4\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\xCF\x03\x00\x00\x65\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x08\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\xCD\x03\x00\x00\xAA\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xD3\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xD3\x0D\x00\x00\xA4\x11\x00\x00\x00\x0F\x00\x00\xD3\x0D\x00\x00\xD3\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\xC3\x03\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\xC7\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xC5\x03\x00\x00\xCE\x03\x00\x00\x04\x01\x00\x00\xD0\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\x00\x00\xB8\x23boxDestroy',0,b'\x00\x00\x67\x23getLeptonicaVersion',0,b'\x00\x00\xBE\x23lept_free',0,b'\x00\x00\x69\x23makePixelSumTab8',0,b'\x00\x00\x16\x23pixAnd',0,b'\x00\x00\x23\x23pixBackgroundNorm',0,b'\x00\x00\x1B\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x0D\x23pixClipRectangle',0,b'\x00\x00\x94\x23pixColorFraction',0,b'\x00\x00\x59\x23pixColorMagnitude',0,b'\x00\x00\x0A\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6B\x23pixCorrelationBinary',0,b'\x00\x00\x80\x23pixCountPixels',0,b'\x00\x00\x63\x23pixDeserializeFromMemory',0,b'\x00\x00\x50\x23pixDeskew',0,b'\x00\x00\xBB\x23pixDestroy',0,b'\x00\x00\x0A\x23pixEndianByteSwapNew',0,b'\x00\x00\x00\x23pixFindPageForeground',0,b'\x00\x00\x7B\x23pixFindSkew',0,b'\x00\x00\x2F\x23pixGammaTRC',0,b'\x00\x00\x8D\x23pixGenerateCIData',0,b'\x00\x00\x70\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x36\x23pixGlobalNormRGB',0,b'\x00\x00\x12\x23pixInvert',0,b'\x00\x00\x54\x23pixMaskOverColorPixels',0,b'\x00\x00\x85\x23pixNumSignificantGrayColors',0,b'\x00\x00\x9D\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x3E\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x60\x23pixRead',0,b'\x00\x00\x50\x23pixRemoveColormap',0,b'\x00\x00\x12\x23pixRotate180',0,b'\x00\x00\x50\x23pixRotateOrth',0,b'\x00\x00\x4B\x23pixScale',0,b'\x00\x00\xA7\x23pixSerializeToMemory',0,b'\x00\x00\xAC\x23pixWriteImpliedFormat',0,b'\x00\x00\xB2\x23pixWriteMemPng',0), - _struct_unions = ((b'\x00\x00\x00\xC1\x00\x00\x00\x02Box',b'\x00\x00\x02\x11x',b'\x00\x00\x02\x11y',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\xD0\x11refcount'),(b'\x00\x00\x00\xC3\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x02\x11type',b'\x00\x00\xCD\x11datacomp',b'\x00\x00\x65\x11nbytescomp',b'\x00\x00\xC6\x11data85',b'\x00\x00\x65\x11nbytes85',b'\x00\x00\xC6\x11cmapdata85',b'\x00\x00\xC6\x11cmapdatahex',b'\x00\x00\x02\x11ncolors',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\x02\x11bps',b'\x00\x00\x02\x11spp',b'\x00\x00\x02\x11minisblack',b'\x00\x00\x02\x11predictor',b'\x00\x00\x65\x11nbytes',b'\x00\x00\x02\x11res'),(b'\x00\x00\x00\xC4\x00\x00\x00\x02Pix',b'\x00\x00\xD0\x11w',b'\x00\x00\xD0\x11h',b'\x00\x00\xD0\x11d',b'\x00\x00\xD0\x11spp',b'\x00\x00\xD0\x11wpl',b'\x00\x00\xD0\x11refcount',b'\x00\x00\x02\x11xres',b'\x00\x00\x02\x11yres',b'\x00\x00\x02\x11informat',b'\x00\x00\x02\x11special',b'\x00\x00\xC6\x11text',b'\x00\x00\xCC\x11colormap',b'\x00\x00\xCF\x11data'),(b'\x00\x00\x00\xC5\x00\x00\x00\x02PixColormap',b'\x00\x00\xBF\x11array',b'\x00\x00\x02\x11depth',b'\x00\x00\x02\x11nalloc',b'\x00\x00\x02\x11n')), - _typenames = (b'\x00\x00\x00\xC1BOX',b'\x00\x00\x00\xC3L_COMP_DATA',b'\x00\x00\x00\xC4PIX',b'\x00\x00\x00\xC5PIXCMAP',b'\x00\x00\x00\x1Fl_float32',b'\x00\x00\x00\xC8l_float64',b'\x00\x00\x00\xCAl_int16',b'\x00\x00\x00\x02l_int32',b'\x00\x00\x00\xC9l_int64',b'\x00\x00\x00\xCBl_int8',b'\x00\x00\x00\xD2l_uint16',b'\x00\x00\x00\xD0l_uint32',b'\x00\x00\x00\xD1l_uint64',b'\x00\x00\x00\xCEl_uint8'), + _types = b'\x00\x00\x0F\x0D\x00\x00\xC4\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC7\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\xC1\x03\x00\x00\x0F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x02\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x08\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xD2\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xC6\x0D\x00\x00\x00\x0F\x00\x00\x49\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x1F\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x49\x11\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC2\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x01\x03\x00\x00\xA4\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\xD1\x03\x00\x00\x65\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x08\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\xCF\x03\x00\x00\xAA\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xD5\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xD5\x0D\x00\x00\xA4\x11\x00\x00\x00\x0F\x00\x00\xD5\x0D\x00\x00\xD5\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\xC3\x03\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\xC7\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xC5\x03\x00\x00\xD0\x03\x00\x00\x04\x01\x00\x00\xD2\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_BASED_ON_SRC',4,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_BINARY',0,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_FULL_COLOR',2,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_GRAYSCALE',1,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_WITH_ALPHA',3,b'\x00\x00\xB8\x23boxDestroy',0,b'\x00\x00\x67\x23getLeptonicaVersion',0,b'\x00\x00\xBE\x23lept_free',0,b'\x00\x00\x69\x23makePixelSumTab8',0,b'\x00\x00\x16\x23pixAnd',0,b'\x00\x00\x23\x23pixBackgroundNorm',0,b'\x00\x00\x1B\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x0D\x23pixClipRectangle',0,b'\x00\x00\x94\x23pixColorFraction',0,b'\x00\x00\x59\x23pixColorMagnitude',0,b'\x00\x00\x0A\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6B\x23pixCorrelationBinary',0,b'\x00\x00\x80\x23pixCountPixels',0,b'\x00\x00\x63\x23pixDeserializeFromMemory',0,b'\x00\x00\x50\x23pixDeskew',0,b'\x00\x00\xBB\x23pixDestroy',0,b'\x00\x00\x0A\x23pixEndianByteSwapNew',0,b'\x00\x00\x00\x23pixFindPageForeground',0,b'\x00\x00\x7B\x23pixFindSkew',0,b'\x00\x00\x2F\x23pixGammaTRC',0,b'\x00\x00\x8D\x23pixGenerateCIData',0,b'\x00\x00\x70\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x36\x23pixGlobalNormRGB',0,b'\x00\x00\x12\x23pixInvert',0,b'\x00\x00\x54\x23pixMaskOverColorPixels',0,b'\x00\x00\x85\x23pixNumSignificantGrayColors',0,b'\x00\x00\x9D\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x3E\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x60\x23pixRead',0,b'\x00\x00\x50\x23pixRemoveColormap',0,b'\x00\x00\x12\x23pixRotate180',0,b'\x00\x00\x50\x23pixRotateOrth',0,b'\x00\x00\x4B\x23pixScale',0,b'\x00\x00\xA7\x23pixSerializeToMemory',0,b'\x00\x00\xAC\x23pixWriteImpliedFormat',0,b'\x00\x00\xB2\x23pixWriteMemPng',0), + _struct_unions = ((b'\x00\x00\x00\xC1\x00\x00\x00\x02Box',b'\x00\x00\x02\x11x',b'\x00\x00\x02\x11y',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\xD2\x11refcount'),(b'\x00\x00\x00\xC3\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x02\x11type',b'\x00\x00\xCF\x11datacomp',b'\x00\x00\x65\x11nbytescomp',b'\x00\x00\xC6\x11data85',b'\x00\x00\x65\x11nbytes85',b'\x00\x00\xC6\x11cmapdata85',b'\x00\x00\xC6\x11cmapdatahex',b'\x00\x00\x02\x11ncolors',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\x02\x11bps',b'\x00\x00\x02\x11spp',b'\x00\x00\x02\x11minisblack',b'\x00\x00\x02\x11predictor',b'\x00\x00\x65\x11nbytes',b'\x00\x00\x02\x11res'),(b'\x00\x00\x00\xC4\x00\x00\x00\x02Pix',b'\x00\x00\xD2\x11w',b'\x00\x00\xD2\x11h',b'\x00\x00\xD2\x11d',b'\x00\x00\xD2\x11spp',b'\x00\x00\xD2\x11wpl',b'\x00\x00\xD2\x11refcount',b'\x00\x00\x02\x11xres',b'\x00\x00\x02\x11yres',b'\x00\x00\x02\x11informat',b'\x00\x00\x02\x11special',b'\x00\x00\xC6\x11text',b'\x00\x00\xCE\x11colormap',b'\x00\x00\xD1\x11data'),(b'\x00\x00\x00\xC5\x00\x00\x00\x02PixColormap',b'\x00\x00\xBF\x11array',b'\x00\x00\x02\x11depth',b'\x00\x00\x02\x11nalloc',b'\x00\x00\x02\x11n')), + _enums = (b'\x00\x00\x00\xC9\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x00\xCA\x00\x00\x00\x16$2\x00REMOVE_CMAP_TO_BINARY,REMOVE_CMAP_TO_GRAYSCALE,REMOVE_CMAP_TO_FULL_COLOR,REMOVE_CMAP_WITH_ALPHA,REMOVE_CMAP_BASED_ON_SRC'), + _typenames = (b'\x00\x00\x00\xC1BOX',b'\x00\x00\x00\xC3L_COMP_DATA',b'\x00\x00\x00\xC4PIX',b'\x00\x00\x00\xC5PIXCMAP',b'\x00\x00\x00\x1Fl_float32',b'\x00\x00\x00\xC8l_float64',b'\x00\x00\x00\xCCl_int16',b'\x00\x00\x00\x02l_int32',b'\x00\x00\x00\xCBl_int64',b'\x00\x00\x00\xCDl_int8',b'\x00\x00\x00\xD4l_uint16',b'\x00\x00\x00\xD2l_uint32',b'\x00\x00\x00\xD3l_uint64',b'\x00\x00\x00\xD0l_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 9d4adb46..68ce3740 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -72,6 +72,16 @@ struct Box }; typedef struct Box BOX; + +/*! Pdf formatted encoding types */ +enum { + L_DEFAULT_ENCODE = 0, /*!< use default encoding based on image */ + L_JPEG_ENCODE = 1, /*!< use dct encoding: 8 and 32 bpp, no cmap */ + L_G4_ENCODE = 2, /*!< use ccitt g4 fax encoding: 1 bpp */ + L_FLATE_ENCODE = 3, /*!< use flate encoding: any depth, cmap ok */ + L_JP2K_ENCODE = 4 /*!< use jp2k encoding: 8 and 32 bpp, no cmap */ +}; + struct L_Compressed_Data { l_int32 type; /*!< encoding type: L_JPEG_ENCODE, etc */ @@ -93,6 +103,15 @@ struct L_Compressed_Data }; typedef struct L_Compressed_Data L_COMP_DATA; +enum { + REMOVE_CMAP_TO_BINARY = 0, /*!< remove colormap for conv to 1 bpp */ + REMOVE_CMAP_TO_GRAYSCALE = 1, /*!< remove colormap for conv to 8 bpp */ + REMOVE_CMAP_TO_FULL_COLOR = 2, /*!< remove colormap for conv to 32 bpp */ + REMOVE_CMAP_WITH_ALPHA = 3, /*!< remove colormap and alpha */ + REMOVE_CMAP_BASED_ON_SRC = 4 /*!< remove depending on src format */ +}; + + """) ffibuilder.cdef(""" diff --git a/src/ocrmypdf/pipeline.py b/src/ocrmypdf/pipeline.py index 2bcceb40..c9015bfd 100644 --- a/src/ocrmypdf/pipeline.py +++ b/src/ocrmypdf/pipeline.py @@ -1070,10 +1070,12 @@ def optimize_pdf( # Extract images we can improve PAGE_GROUP_SIZE = 10 + SIMPLE_COLORSPACES = ('DeviceRGB', 'DeviceGray', 'CalRGB', 'CalGray') changed_xrefs = set() from collections import defaultdict jbig2_groups = defaultdict(lambda: []) jpegs = [] + pngs = [] for pageno in range(doc.pageCount): group, index = divmod(pageno, PAGE_GROUP_SIZE) page_images = doc.getPageImageList(pageno) @@ -1087,14 +1089,26 @@ def optimize_pdf( pix.writePNG(make_img_name(xref), savealpha=False) changed_xrefs.add(xref) jbig2_groups[group].append(xref) - elif filt == 'DCTDecode': + elif filt == 'DCTDecode' and cs in SIMPLE_COLORSPACES: raw_jpeg = pike._get_object_id(xref, 0) - if raw_jpeg.stream_dict.get('/ColorTransform', 1) != 1: - continue # Don't mess with JPEGs other than YUV + try: + if raw_jpeg.DecodeParms.ColorTransform != 1: + continue # Don't mess with JPEGs other than YUV + except AttributeError: + pass raw_jpeg_data = raw_jpeg.read_raw_bytes() (root / '{:08d}.jpg'.format(xref)).write_bytes(raw_jpeg_data) changed_xrefs.add(xref) jpegs.append(xref) + elif filt == 'FlateDecode' and cs in SIMPLE_COLORSPACES: + # raw_png = pike._get_object_id(xref, 0) + # raw_png_data = raw_png.read_raw_bytes() + # (root / '{:08d}.png'.format(xref)).write_bytes(raw_png_data) + pix = fitz.Pixmap(doc, xref) + pix.writePNG(make_img_name(xref), savealpha=False) + changed_xrefs.add(xref) + pngs.append(xref) + from subprocess import run, PIPE import concurrent.futures @@ -1104,7 +1118,7 @@ def optimize_pdf( futures = [] for group, xrefs in jbig2_groups.items(): prefix = 'group{:08d}'.format(group) - cmd = ['jbig2', '-b', prefix, '-s', '-p', '-S'] + cmd = ['jbig2', '-b', prefix, '-s', '-p'] cmd.extend(make_img_name(xref) for xref in xrefs) future = executor.submit( run, cmd, cwd=str(root), stdout=PIPE, stderr=PIPE) @@ -1138,14 +1152,30 @@ def optimize_pdf( for xref in jpegs: pix = leptonica.Pix.read((root / '{:08d}.jpg'.format(xref))) - compdata = pix.generate_pdf_data(1, 10) + compdata = pix.generate_pdf_data(leptonica.lept.L_JPEG_ENCODE, 10) im_obj = pike._get_object_id(xref, 0) im_obj.write( compdata.read(), pikepdf.Name('/DCTDecode'), pikepdf.Null() ) + for xref in pngs: + pix = leptonica.Pix.read((root / '{:08d}.png'.format(xref))) + compdata = pix.generate_pdf_data(leptonica.lept.L_FLATE_ENCODE, 0) + im_obj = pike._get_object_id(xref, 0) + im_obj.write( + compdata.read(), pikepdf.Name('/FlateDecode'), + pikepdf.Dictionary({ + '/Predictor': compdata.predictor + }) + ) + + pike.save(output_file, preserve_pdfa=True) + input_size = Path(input_file).stat().st_size + output_size = Path(output_file).stat().st_size + improvement = 100.0 * ((input_size/output_size) - 1) + log.info("Optimize reduced size by {:.1f}".format(improvement)) def merge_sidecars(