diff --git a/docs/release_notes.rst b/docs/release_notes.rst index e417f644..abe7183e 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -10,6 +10,12 @@ The OCRmyPDF package itself does not contain a public API, although it is fairly replace: `#$1 `_ +v6.2.3 +------ + +- Fix compatibility with img2pdf >= 0.3.0 by rejecting input images that have an alpha channel + + v6.2.2 ------ diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index 0117cb4d..3ab8c5b1 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -105,7 +105,7 @@ def is_file_writable(test_file): if p.is_symlink(): # Python 3.5 does not accept parameters for Path.resolve() and behaves - # as if strict=True (throws an exception on failure). Python 3.6 + # as if strict=True (throws an exception on failure). Python 3.6 # defaults to strict=False. This implements strict=False like behavior # for Python 3.5. if sys.version_info[0:2] <= (3, 5): @@ -130,15 +130,6 @@ def is_file_writable(test_file): return True -def discard_alpha(im): - if im.mode in ('RGBA', 'LA'): - fill_color = (1, 1, 1) if im.mode == 'RGBA' else 1 - backgd = Image.new(im.mode[:-1], im.size, fill_color) - backgd.paste(im, im.split()[-1]) - im = backgd - return im - - if sys.version_info[0:2] <= (3, 5): def universal_open(p, *args, **kwargs): "Work around Python 3.5's inability to open(pathlib.Path())" diff --git a/src/ocrmypdf/pipeline.py b/src/ocrmypdf/pipeline.py index 49145990..86a1f10e 100644 --- a/src/ocrmypdf/pipeline.py +++ b/src/ocrmypdf/pipeline.py @@ -34,7 +34,7 @@ from ruffus import formatter, regex, Pipeline, suffix from .hocrtransform import HocrTransform from .pdfinfo import PdfInfo, Encoding, Colorspace from .pdfa import generate_pdfa_ps, encode_pdf_date -from .helpers import re_symlink, is_iterable_notstr, page_number, discard_alpha +from .helpers import re_symlink, is_iterable_notstr, page_number from .exec import ghostscript, tesseract, qpdf from .lib import fitz from .exceptions import PdfMergeFailedError, UnsupportedImageFormatError, \ @@ -145,6 +145,13 @@ def triage_image_file(input_file, output_file, log, options): "image was scanned and specify it using --image-dpi.") raise DpiError() + if im.mode in ('RGBA', 'LA'): + log.error( + "The input image has an alpha channel. Remove the alpha " + "channel first." + ) + raise UnsupportedImageFormatError() + if 'iccprofile' not in im.info: if im.mode == 'RGB': log.info('Input image has no ICC profile, assuming sRGB') @@ -160,14 +167,8 @@ def triage_image_file(input_file, output_file, log, options): layout_fun = img2pdf.get_fixed_dpi_layout_fun( (options.image_dpi, options.image_dpi)) with open(output_file, 'wb') as outf: - im = Image.open(input_file) - im_format = im.format - im = discard_alpha(im) - im_bio = BytesIO() - im.save(im_bio, format=im_format) - im_bio.seek(0) img2pdf.convert( - im_bio, + input_file, layout_fun=layout_fun, with_pdfrw=False, outputstream=outf) @@ -179,7 +180,7 @@ def triage_image_file(input_file, output_file, log, options): def _pdf_guess_version(input_file, search_window=1024): """Try to find version signature at start of file. - + Not robust enough to deal with appended files. Returns empty string if not found, indicating file is probably not PDF. @@ -238,7 +239,7 @@ def repair_and_parse_pdf( "output these files.) Use --output-type=pdf instead." ) raise InputFileError() - + if len(pdfinfo.pages) > 2000 and sys.version_info[0:2] <= (3, 5): log.warning( "Performance regressions are known occur with Python 3.5 for " @@ -305,7 +306,7 @@ def is_ocr_required(pageinfo, log, options): # We found a page with no images and no text. That means it may # have vector art that the user wants to OCR. If we determined # lossless reconstruction is not possible then we have to rasterize - # the image. So if OCR is being forced, take that to mean YES, go + # the image. So if OCR is being forced, take that to mean YES, go # ahead and rasterize. If not forced, then pretend there's no text # on the page at all so we don't lose anything. # This could be made smarter by explicitly searching for vector art. @@ -891,7 +892,7 @@ def get_pdfmark(base_pdf, options): except (KeyError, TypeError): return '' - pdfmark = {k: from_document_info(k) for k in + pdfmark = {k: from_document_info(k) for k in ('/Title', '/Author', '/Keywords', '/Subject', '/CreationDate')} if options.title: pdfmark['/Title'] = options.title @@ -1082,7 +1083,7 @@ def merge_sidecars( if txt_file: with open(txt_file, 'r', encoding="utf-8") as in_: txt = in_.read() - # Tesseract v4 alpha started adding form feeds in + # Tesseract v4 alpha started adding form feeds in # commit aa6eb6b # No obvious way to detect what binaries will do this, so # for consistency just ignore its form feeds and insert our @@ -1116,7 +1117,7 @@ def copy_final( sys.stdout.flush() else: # At this point we overwrite the output_file specified by the user - # use copyfileobj because then we use open() to create the file and + # use copyfileobj because then we use open() to create the file and # get the appropriate umask, ownership, etc. with open(output_file, 'wb') as output_stream: copyfileobj(input_stream, output_stream) @@ -1291,7 +1292,7 @@ def build_pipeline(options, work_folder, log, context): output=os.path.join(work_folder, r'\1.rendered.pdf'), extras=[log, context]) task_combine_layers.graphviz(fillcolor='"#00cc66"') - task_combine_layers.active_if(options.pdf_renderer == 'hocr' or + task_combine_layers.active_if(options.pdf_renderer == 'hocr' or options.pdf_renderer == 'sandwich') # Tesseract OCR+PDF diff --git a/tests/resources/baiona.png b/tests/resources/baiona.png index 3e4b0058..aa6cb017 100644 Binary files a/tests/resources/baiona.png and b/tests/resources/baiona.png differ diff --git a/tests/resources/baiona_alpha.png b/tests/resources/baiona_alpha.png new file mode 100644 index 00000000..3e4b0058 Binary files /dev/null and b/tests/resources/baiona_alpha.png differ diff --git a/tests/test_main.py b/tests/test_main.py index 78afdff1..ec438c0b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -829,6 +829,7 @@ def test_no_contents(spoof_tesseract_noop, resources, outpdf): @pytest.mark.parametrize('image', [ 'baiona.png', 'baiona_gray.png', + 'baiona_alpha.png', 'congress.jpg' ]) def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec, @@ -839,7 +840,6 @@ def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec, output_file = str(outpdf) im = Image.open(input_file) - # Runs: ocrmypdf - output.pdf < testfile with open(input_file, 'rb') as input_stream: p_args = ocrmypdf_exec + [ @@ -849,6 +849,11 @@ def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec, stdin=input_stream, env=spoof_tesseract_noop) out, err = p.communicate() + if im.mode in ('RGBA', 'LA'): + # If alpha image is input, expect an error + assert p.returncode != ExitCode.ok and b'alpha' in err + return + assert p.returncode == ExitCode.ok, err.decode('utf-8') pdfinfo = PdfInfo(output_file) @@ -894,7 +899,7 @@ def test_compression_changed(spoof_tesseract_noop, ocrmypdf_exec, stdin=input_stream, env=spoof_tesseract_noop) out, err = p.communicate() - assert p.returncode == ExitCode.ok + assert p.returncode == ExitCode.ok, err pdfinfo = PdfInfo(output_file)