Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package img2pdf for openSUSE:Factory checked in at 2026-03-18 16:51:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/img2pdf (Old) and /work/SRC/openSUSE:Factory/.img2pdf.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "img2pdf" Wed Mar 18 16:51:47 2026 rev:6 rq:1340784 version:0.6.3 Changes: -------- --- /work/SRC/openSUSE:Factory/img2pdf/img2pdf.changes 2025-04-30 19:05:19.420680508 +0200 +++ /work/SRC/openSUSE:Factory/.img2pdf.new.8177/img2pdf.changes 2026-03-18 16:53:38.848877022 +0100 @@ -1,0 +2,9 @@ +Tue Mar 17 12:46:39 UTC 2026 - Dirk Müller <[email protected]> + +- update to 0.6.3: + * add pyproject.toml + * merge jp2 module into img2pdf + * Add support for TIFF images with alpha channel + * Avoid division by zero aspect ratio + +------------------------------------------------------------------- Old: ---- img2pdf-0.6.1.tar.gz New: ---- img2pdf-0.6.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ img2pdf.spec ++++++ --- /var/tmp/diff_new_pack.y0dbwK/_old 2026-03-18 16:53:39.868919619 +0100 +++ /var/tmp/diff_new_pack.y0dbwK/_new 2026-03-18 16:53:39.872919787 +0100 @@ -1,7 +1,7 @@ # # spec file for package img2pdf # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: img2pdf -Version: 0.6.1 +Version: 0.6.3 Release: 0 Summary: Python module for converting images to PDF via direct JPEG inclusion License: LGPL-3.0-or-later ++++++ img2pdf-0.6.1.tar.gz -> img2pdf-0.6.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/.gitignore new/img2pdf-0.6.3/.gitignore --- old/img2pdf-0.6.1/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/.gitignore 2024-01-16 19:44:19.177303300 +0100 @@ -0,0 +1,6 @@ +*.pyc +build +src/*.egg-info + +.eggs +.tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/.mailmap new/img2pdf-0.6.3/.mailmap --- old/img2pdf-0.6.1/.mailmap 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/.mailmap 2024-01-16 19:44:19.177303300 +0100 @@ -0,0 +1,3 @@ +Johannes Schauer Marin Rodrigues <[email protected]> +Johannes Schauer Marin Rodrigues <[email protected]> <[email protected]> +Johannes Schauer Marin Rodrigues <[email protected]> <[email protected]> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/.travis.yml new/img2pdf-0.6.3/.travis.yml --- old/img2pdf-0.6.1/.travis.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/.travis.yml 2024-01-16 19:44:19.177303300 +0100 @@ -0,0 +1,42 @@ +language: python +matrix: + include: + - name: "Ubuntu Focal" + dist: focal + addons: + apt: + packages: + - imagemagick + - libtiff-tools + - libimage-exiftool-perl + - poppler-utils + - netpbm + - ghostscript + - mupdf-tools + - name: "python 3.9 Windows" + os: windows + language: shell # 'language: python' is an error on Travis CI Windows + before_install: choco install python imagemagick + env: PATH=/c/Python39:/c/Python39/Scripts:$PATH + - name: "python 3.7 MacOs" + os: osx + osx_image: xcode12.2 # pikepdf import fails with earlier versions + language: shell # 'language: python' is an error on Travis CI macOS + cache: + directories: + - "$HOME/Library/Caches/Homebrew" + - "$HOME/Library/Caches/pip" + addons: + homebrew: + #update: true + packages: + - python3 + - imagemagick + before_install: + - python3 -m pip install --upgrade virtualenv + - virtualenv -p python3 --system-site-packages "$HOME/venv" + - source "$HOME/venv/bin/activate" +install: pip install tox +script: + - python --version + - python -m tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/CHANGES.rst new/img2pdf-0.6.3/CHANGES.rst --- old/img2pdf-0.6.1/CHANGES.rst 2025-04-27 18:53:51.000000000 +0200 +++ new/img2pdf-0.6.3/CHANGES.rst 2025-11-05 21:47:18.335115400 +0100 @@ -2,6 +2,18 @@ CHANGES ======= +0.6.3 (2025-11-06) +------------------ + + - add pyproject.toml + - merge jp2 module into img2pdf + +0.6.2 (2025-11-05) +------------------ + + - Add support for TIFF images with alpha channel + - Avoid division by zero aspect ratio + 0.6.1 (2025-04-27) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/HACKING new/img2pdf-0.6.3/HACKING --- old/img2pdf-0.6.1/HACKING 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/HACKING 2025-11-05 21:49:57.458368800 +0100 @@ -0,0 +1,85 @@ +Running img2pdf from source +--------------------------- + +img2pdf can be run directly from the cloned git repository: + + $ python3 src/img2pdf.py img.jpg -o out.pdf + +Running the testsuite +--------------------- + + $ pytest + +Making a new release +-------------------- + + - CHANGES.rst: Add a new entry + - setup.py: Bump VERSION + - src/img2pdf.py: Bump __version__ + - Commit: + + $ git add CHANGES.rst setup.py src/img2pdf.py + $ git commit -m "release version X.Y.Z" + + - Add git tag: + + $ git tag X.Y.Z -m X.Y.Z + + - Build and upload to pypi: + + $ git clean -fdx + $ flit build + $ flit publish + + - Push everything to git forge + + $ git push + + - Push to github + + $ git push github + + - Obtain img2pdf.exe from appveyor: + + https://ci.appveyor.com/project/josch/img2pdf/ + + - Create new release: + + https://gitlab.mister-muffin.de/josch/img2pdf/releases/new + +Using debbisect to find regressions +----------------------------------- + + $ debbisect --cache=./cache --depends="git,ca-certificates,python3, + ghostscript,imagemagick,mupdf-tools,poppler-utils,python3-pil, + python3-pytest,python3-numpy,python3-scipy,python3-pikepdf" \ + --verbose 2023-09-16 2023-10-24 \ + 'chroot "$1" sh -c " + git clone https://gitlab.mister-muffin.de/josch/img2pdf.git + && cd img2pdf + && pytest 'src/img2pdf_test.py::test_jpg_2000_rgba8[internal]"' + +Using debbisect cache +--------------------- + + $ mmdebstrap --variant=apt --aptopt='Acquire::Check-Valid-Until "false"' \ + --include=git,ca-certificates,python3,ghostscript,imagemagick \ + --include=mupdf-tools,poppler-utils,python3-pil,python3-pytest \ + --include=python3-numpy,python3-scipy,python3-pikepdf \ + --hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount \ + --setup-hook='mkdir -p "$1/home/josch/git/devscripts/cache/pool/"' \ + --setup-hook='mount -o ro,bind /home/josch/git/devscripts/cache/pool/ "$1/home/josch/git/devscripts/cache/pool/"' \ + --chrooted-customize-hook=bash + unstable /dev/null + file:///home/josch/git/devscripts/cache/archive/debian/20231022T090139Z/ + +Bisecting imagemagick +--------------------- + +Imagemagick can be built with minimal dependencies. To build with support for +jpeg images, installing build-essential, pkgconf and libjpeg-dev is sufficient. + + $ git clean -fdx && git reset --hard + $ ./configure + $ make -j$(nproc) utilities/magick + $ ./magick.sh magick compare ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/PKG-INFO new/img2pdf-0.6.3/PKG-INFO --- old/img2pdf-0.6.1/PKG-INFO 2025-04-27 18:55:03.534961500 +0200 +++ new/img2pdf-0.6.3/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,28 +1,15 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: img2pdf -Version: 0.6.1 -Summary: Convert images to PDF via direct JPEG inclusion. -Home-page: https://gitlab.mister-muffin.de/josch/img2pdf -Download-URL: https://gitlab.mister-muffin.de/josch/img2pdf/repository/archive.tar.gz?ref=0.6.1 -Author: Johannes Schauer Marin Rodrigues -Author-email: [email protected] -License: LGPL -Keywords: jpeg pdf converter -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Other Audience -Classifier: Environment :: Console -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent +Version: 0.6.3 +Summary: Lossless conversion of raster images to PDF. +Author-email: Johannes Schauer Marin Rodrigues <[email protected]> +Requires-Python: >=3.5 Description-Content-Type: text/markdown -Provides-Extra: gui +Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) License-File: LICENSE +Requires-Dist: Pillow +Requires-Dist: pikepdf +Project-URL: Home, https://gitlab.mister-muffin.de/josch/img2pdf [](https://app.travis-ci.com/josch/img2pdf) [](https://ci.appveyor.com/project/josch/img2pdf/branch/main) @@ -353,3 +340,4 @@ to feed it 16bit files, it errors out with Unhandled bps/spp combination. It also seems to choose JPEG encoding when using it on some file types (like palette images) making it again not lossless for that input as well. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/appveyor.yml new/img2pdf-0.6.3/appveyor.yml --- old/img2pdf-0.6.1/appveyor.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/appveyor.yml 2024-08-07 07:14:01.019593000 +0200 @@ -0,0 +1,33 @@ +environment: + # For Python versions available on Appveyor, see + # https://www.appveyor.com/docs/windows-images-software/#python + matrix: +# - PYTHON: "C:\\Python27" +# - PYTHON: "C:\\Python33" +# - PYTHON: "C:\\Python34" +# - PYTHON: "C:\\Python35" +# - PYTHON: "C:\\Python36" +# - PYTHON: "C:\\Python37" +# - PYTHON: "C:\\Python27-x64" +# - PYTHON: "C:\\Python33-x64" +# - PYTHON: "C:\\Python34-x64" +# - PYTHON: "C:\\Python35-x64" +# - PYTHON: "C:\\Python36-x64" + - PYTHON: "C:\\Python37-x64" + +install: + - "%PYTHON%\\python.exe -m pip install tox wheel pyinstaller Pillow" + +build: off + +# don't run tests on windows because we don't have imagemagick +#test_script: +# - "%PYTHON%\\python.exe -m tox" + +after_test: + - "%PYTHON%\\python.exe setup.py bdist_wheel" + - "%PYTHON%\\python.exe -m PyInstaller --clean --onefile --console --nowindowed --name img2pdf src/img2pdf.py" + #- "%PYTHON%\\python.exe -m PyInstaller --clean --onefile --noconsole --windowed --name img2pdf_windowed src/img2pdf.py" + +artifacts: + - path: dist\* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/pyproject.toml new/img2pdf-0.6.3/pyproject.toml --- old/img2pdf-0.6.1/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/pyproject.toml 2025-11-05 18:13:02.808634800 +0100 @@ -0,0 +1,25 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "img2pdf" +authors = [{name = "Johannes Schauer Marin Rodrigues", email = "[email protected]"}] +readme = "README.md" +license = {file = "LICENSE"} +requires-python = ">=3.5" +classifiers = ["License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)"] +dynamic = ["version", "description"] +dependencies = [ + "Pillow", + "pikepdf" +] + +[project.urls] +Home = "https://gitlab.mister-muffin.de/josch/img2pdf" + +[project.scripts] +img2pdf = "img2pdf:main" + +[project.gui-scripts] +img2pdf-gui = "img2pdf:gui" Binary files old/img2pdf-0.6.1/screenshot.png and new/img2pdf-0.6.3/screenshot.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/setup.cfg new/img2pdf-0.6.3/setup.cfg --- old/img2pdf-0.6.1/setup.cfg 2025-04-27 18:55:03.534961500 +0200 +++ new/img2pdf-0.6.3/setup.cfg 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/setup.py new/img2pdf-0.6.3/setup.py --- old/img2pdf-0.6.1/setup.py 2025-04-27 18:53:56.000000000 +0200 +++ new/img2pdf-0.6.3/setup.py 2025-11-05 21:47:29.063334500 +0100 @@ -1,7 +1,7 @@ import sys from setuptools import setup -VERSION = "0.6.1" +VERSION = "0.6.3" INSTALL_REQUIRES = ( "Pillow", @@ -36,7 +36,7 @@ download_url="https://gitlab.mister-muffin.de/josch/img2pdf/repository/" "archive.tar.gz?ref=" + VERSION, package_dir={"": "src"}, - py_modules=["img2pdf", "jp2"], + py_modules=["img2pdf"], include_package_data=True, zip_safe=True, install_requires=INSTALL_REQUIRES, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/PKG-INFO new/img2pdf-0.6.3/src/img2pdf.egg-info/PKG-INFO --- old/img2pdf-0.6.1/src/img2pdf.egg-info/PKG-INFO 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,355 +0,0 @@ -Metadata-Version: 2.1 -Name: img2pdf -Version: 0.6.1 -Summary: Convert images to PDF via direct JPEG inclusion. -Home-page: https://gitlab.mister-muffin.de/josch/img2pdf -Download-URL: https://gitlab.mister-muffin.de/josch/img2pdf/repository/archive.tar.gz?ref=0.6.1 -Author: Johannes Schauer Marin Rodrigues -Author-email: [email protected] -License: LGPL -Keywords: jpeg pdf converter -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Other Audience -Classifier: Environment :: Console -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Description-Content-Type: text/markdown -Provides-Extra: gui -License-File: LICENSE - -[](https://app.travis-ci.com/josch/img2pdf) -[](https://ci.appveyor.com/project/josch/img2pdf/branch/main) - -img2pdf -======= - -Lossless conversion of raster images to PDF. You should use img2pdf if your -priorities are (in this order): - - 1. **always lossless**: the image embedded in the PDF will always have the - exact same color information for every pixel as the input - 2. **small**: if possible, the difference in filesize between the input image - and the output PDF will only be the overhead of the PDF container itself - 3. **fast**: if possible, the input image is just pasted into the PDF document - as-is without any CPU hungry re-encoding of the pixel data - -Conventional conversion software (like ImageMagick) would either: - - 1. not be lossless because lossy re-encoding to JPEG - 2. not be small because using wasteful flate encoding of raw pixel data - 3. not be fast because input data gets re-encoded - -Another advantage of not having to re-encode the input (in most common -situations) is, that img2pdf is able to handle much larger input than other -software, because the raw pixel data never has to be loaded into memory. - -The following table shows how img2pdf handles different input depending on the -input file format and image color space. - -| Format | Colorspace | Result | -| ------------------------------------- | ------------------------------------ | ------------- | -| JPEG | any | direct | -| JPEG2000 | any | direct | -| PNG (non-interlaced, no transparency) | any | direct | -| TIFF (CCITT Group 4) | 1-bit monochrome | direct | -| JBIG2 (single-page generic coding) | 1-bit monochrome | direct | -| any | any except CMYK and 1-bit monochrome | PNG Paeth | -| any | 1-bit monochrome | CCITT Group 4 | -| any | CMYK | flate | - -For JPEG, JPEG2000, non-interlaced PNG, TIFF images with CCITT Group 4 -encoded data, and JBIG2 with single-page generic coding (e.g. using `jbig2enc`), -img2pdf directly embeds the image data into the PDF without -re-encoding it. It thus treats the PDF format merely as a container format for -the image data. In these cases, img2pdf only increases the filesize by the size -of the PDF container (typically around 500 to 700 bytes). Since data is only -copied and not re-encoded, img2pdf is also typically faster than other -solutions for these input formats. - -For all other input types, img2pdf first has to transform the pixel data to -make it compatible with PDF. In most cases, the PNG Paeth filter is applied to -the pixel data. For 1-bit monochrome input, CCITT Group 4 is used instead. Only for -CMYK input no filter is applied before finally applying flate compression. - -Usage ------ - -The images must be provided as files because img2pdf needs to seek in the file -descriptor. - -If no output file is specified with the `-o`/`--output` option, output will be -done to stdout. A typical invocation is: - - $ img2pdf img1.png img2.jpg -o out.pdf - -The detailed documentation can be accessed by running: - - $ img2pdf --help - -With no command line arguments supplied, img2pdf will read a single image from -standard input and write the resulting PDF to standard output. Here is an -example for how to scan directly to PDF using scanimage(1) from SANE: - - $ scanimage --mode=Color --resolution=300 | pnmtojpeg -quality 90 | img2pdf > scan.pdf - -Bugs ----- - - - If you find a JPEG, JPEG2000, PNG or CCITT Group 4 encoded TIFF file that, - when embedded into the PDF cannot be read by the Adobe Acrobat Reader, - please contact me. - - - An error is produced if the input image is broken. This commonly happens if - the input image has an invalid EXIF Orientation value of zero. Even though - only nine different values from 1 to 9 are permitted, Anroid phones and - Canon DSLR cameras produce JPEG images with the invalid value of zero. - Either fix your input images with `exiftool` or similar software before - passing the JPEG to `img2pdf` or run `img2pdf` with `--rotation=ifvalid` - (if you run img2pdf from the commandline) or by passing - `rotation=img2pdf.Rotation.ifvalid` as an argument to `convert()` when using - img2pdf as a library. - - - img2pdf uses PIL (or Pillow) to obtain image meta data and to convert the - input if necessary. To prevent decompression bomb denial of service attacks, - Pillow limits the maximum number of pixels an input image is allowed to - have. If you are sure that you know what you are doing, then you can disable - this safeguard by passing the `--pillow-limit-break` option to img2pdf. This - allows one to process even very large input images. - -Installation ------------- - -On a Debian- and Ubuntu-based systems, img2pdf can be installed from the -official repositories: - - $ apt install img2pdf - -If you want to install it using pip, you can run: - - $ pip3 install img2pdf - -If you prefer to install from source code use: - - $ cd img2pdf/ - $ pip3 install . - -To test the console script without installing the package on your system, -use virtualenv: - - $ cd img2pdf/ - $ virtualenv ve - $ ve/bin/pip3 install . - -You can then test the converter using: - - $ ve/bin/img2pdf -o test.pdf src/tests/test.jpg - -If you don't want to setup Python on Windows, then head to the -[releases](https://gitlab.mister-muffin.de/josch/img2pdf/releases) section and download the latest -`img2pdf.exe`. - -GUI ---- - -There exists an experimental GUI with all settings currently disabled. You can -directly convert images to PDF but you cannot set any options via the GUI yet. -If you are interested in adding more features to the PDF, please submit a merge -request. The GUI is based on tkinter and works on Linux, Windows and MacOS. - - - -Library -------- - -The package can also be used as a library: - - import img2pdf - - # opening from filename - with open("name.pdf","wb") as f: - f.write(img2pdf.convert('test.jpg')) - - # opening from file handle - with open("name.pdf","wb") as f1, open("test.jpg") as f2: - f1.write(img2pdf.convert(f2)) - - # opening using pathlib - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(pathlib.Path('test.jpg'))) - - # using in-memory image data - with open("name.pdf","wb") as f: - f.write(img2pdf.convert("\x89PNG...") - - # multiple inputs (variant 1) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert("test1.jpg", "test2.png")) - - # multiple inputs (variant 2) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(["test1.jpg", "test2.png"])) - - # convert all files ending in .jpg inside a directory - dirname = "/path/to/images" - imgs = [] - for fname in os.listdir(dirname): - if not fname.endswith(".jpg"): - continue - path = os.path.join(dirname, fname) - if os.path.isdir(path): - continue - imgs.append(path) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(imgs)) - - # convert all files ending in .jpg in a directory and its subdirectories - dirname = "/path/to/images" - imgs = [] - for r, _, f in os.walk(dirname): - for fname in f: - if not fname.endswith(".jpg"): - continue - imgs.append(os.path.join(r, fname)) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(imgs)) - - - # convert all files matching a glob - import glob - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(glob.glob("/path/to/*.jpg"))) - - # convert all files matching a glob using pathlib.Path - from pathlib import Path - with open("name.pdf","wb") as f: - f.write(img2pdf.convert(*Path("/path").glob("**/*.jpg"))) - - # ignore invalid rotation values in the input images - with open("name.pdf","wb") as f: - f.write(img2pdf.convert('test.jpg'), rotation=img2pdf.Rotation.ifvalid) - - # writing to file descriptor - with open("name.pdf","wb") as f1, open("test.jpg") as f2: - img2pdf.convert(f2, outputstream=f1) - - # specify paper size (A4) - a4inpt = (img2pdf.mm_to_pt(210),img2pdf.mm_to_pt(297)) - layout_fun = img2pdf.get_layout_fun(a4inpt) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert('test.jpg', layout_fun=layout_fun)) - - # use a fixed dpi of 300 instead of reading it from the image - dpix = dpiy = 300 - layout_fun = img2pdf.get_fixed_dpi_layout_fun((dpix, dpiy)) - with open("name.pdf","wb") as f: - f.write(img2pdf.convert('test.jpg', layout_fun=layout_fun)) - - # create a PDF/A-1b compliant document by passing an ICC profile - with open("name.pdf","wb") as f: - f.write(img2pdf.convert('test.jpg', pdfa="/usr/share/color/icc/sRGB.icc")) - -Comparison to ImageMagick -------------------------- - -Create a large test image: - - $ convert logo: -resize 8000x original.jpg - -Convert it into PDF using ImageMagick and img2pdf: - - $ time img2pdf original.jpg -o img2pdf.pdf - $ time convert original.jpg imagemagick.pdf - -Notice how ImageMagick took an order of magnitude longer to do the conversion -than img2pdf. It also used twice the memory. - -Now extract the image data from both PDF documents and compare it to the -original: - - $ pdfimages -all img2pdf.pdf tmp - $ compare -metric AE original.jpg tmp-000.jpg null: - 0 - $ pdfimages -all imagemagick.pdf tmp - $ compare -metric AE original.jpg tmp-000.jpg null: - 118716 - -To get lossless output with ImageMagick we can use Zip compression but that -unnecessarily increases the size of the output: - - $ convert original.jpg -compress Zip imagemagick.pdf - $ pdfimages -all imagemagick.pdf tmp - $ compare -metric AE original.jpg tmp-000.png null: - 0 - $ stat --format="%s %n" original.jpg img2pdf.pdf imagemagick.pdf - 1535837 original.jpg - 1536683 img2pdf.pdf - 9397809 imagemagick.pdf - -Comparison to pdfLaTeX ----------------------- - -pdfLaTeX performs a lossless conversion from included images to PDF by default. -If the input is a JPEG, then it simply embeds the JPEG into the PDF in the same -way as img2pdf does it. But for other image formats it uses flate compression -of the plain pixel data and thus needlessly increases the output file size: - - $ convert logo: -resize 8000x original.png - $ cat << END > pdflatex.tex - \documentclass{article} - \usepackage{graphicx} - \begin{document} - \includegraphics{original.png} - \end{document} - END - $ pdflatex pdflatex.tex - $ stat --format="%s %n" original.png pdflatex.pdf - 4500182 original.png - 9318120 pdflatex.pdf - -Comparison to podofoimg2pdf ---------------------------- - -Like pdfLaTeX, podofoimg2pdf is able to perform a lossless conversion from JPEG -to PDF by plainly embedding the JPEG data into the pdf container. But just like -pdfLaTeX it uses flate compression for all other file formats, thus sometimes -resulting in larger files than necessary. - - $ convert logo: -resize 8000x original.png - $ podofoimg2pdf out.pdf original.png - stat --format="%s %n" original.png out.pdf - 4500181 original.png - 9335629 out.pdf - -It also only supports JPEG, PNG and TIF as input and lacks many of the -convenience features of img2pdf like page sizes, borders, rotation and -metadata. - -Comparison to Tesseract OCR ---------------------------- - -Tesseract OCR comes closest to the functionality img2pdf provides. It is able -to convert JPEG and PNG input to PDF without needlessly increasing the filesize -and is at the same time lossless. So if your input is JPEG and PNG images, then -you should safely be able to use Tesseract instead of img2pdf. For other input, -Tesseract might not do a lossless conversion. For example it converts CMYK -input to RGB and removes the alpha channel from images with transparency. For -multipage TIFF or animated GIF, it will only convert the first frame. - -Comparison to econvert from ExactImage --------------------------------------- - -Like pdflatex and podofoimg2pf, econvert is able to embed JPEG images into PDF -directly without re-encoding but when given other file formats, it stores them -just using flate compressen, which unnecessarily increases the filesize. -Furthermore, it throws an error with CMYK TIF input. It also doesn't store CMYK -jpeg files as CMYK but converts them to RGB, so it's not lossless. When trying -to feed it 16bit files, it errors out with Unhandled bps/spp combination. It -also seems to choose JPEG encoding when using it on some file types (like -palette images) making it again not lossless for that input as well. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/SOURCES.txt new/img2pdf-0.6.3/src/img2pdf.egg-info/SOURCES.txt --- old/img2pdf-0.6.1/src/img2pdf.egg-info/SOURCES.txt 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,33 +0,0 @@ -CHANGES.rst -LICENSE -MANIFEST.in -README.md -setup.py -test_comp.sh -src/img2pdf.py -src/img2pdf_test.py -src/jp2.py -src/img2pdf.egg-info/PKG-INFO -src/img2pdf.egg-info/SOURCES.txt -src/img2pdf.egg-info/dependency_links.txt -src/img2pdf.egg-info/entry_points.txt -src/img2pdf.egg-info/requires.txt -src/img2pdf.egg-info/top_level.txt -src/img2pdf.egg-info/zip-safe -src/tests/input/CMYK.jpg -src/tests/input/CMYK.tif -src/tests/input/animation.gif -src/tests/input/gray.png -src/tests/input/mono.png -src/tests/input/mono.tif -src/tests/input/normal.jpg -src/tests/input/normal.png -src/tests/output/CMYK.jpg.pdf -src/tests/output/CMYK.tif.pdf -src/tests/output/animation.gif.pdf -src/tests/output/gray.png.pdf -src/tests/output/mono.jb2.pdf -src/tests/output/mono.png.pdf -src/tests/output/mono.tif.pdf -src/tests/output/normal.jpg.pdf -src/tests/output/normal.png.pdf \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/dependency_links.txt new/img2pdf-0.6.3/src/img2pdf.egg-info/dependency_links.txt --- old/img2pdf-0.6.1/src/img2pdf.egg-info/dependency_links.txt 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/entry_points.txt new/img2pdf-0.6.3/src/img2pdf.egg-info/entry_points.txt --- old/img2pdf-0.6.1/src/img2pdf.egg-info/entry_points.txt 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/entry_points.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,8 +0,0 @@ -[console_scripts] -img2pdf = img2pdf:main - -[gui_scripts] -img2pdf-gui = img2pdf:gui - -[setuptools.installation] -eggsecutable = img2pdf:main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/requires.txt new/img2pdf-0.6.3/src/img2pdf.egg-info/requires.txt --- old/img2pdf-0.6.1/src/img2pdf.egg-info/requires.txt 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,5 +0,0 @@ -Pillow -pikepdf - -[gui] -tkinter diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/top_level.txt new/img2pdf-0.6.3/src/img2pdf.egg-info/top_level.txt --- old/img2pdf-0.6.1/src/img2pdf.egg-info/top_level.txt 2025-04-27 18:55:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -img2pdf -jp2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.egg-info/zip-safe new/img2pdf-0.6.3/src/img2pdf.egg-info/zip-safe --- old/img2pdf-0.6.1/src/img2pdf.egg-info/zip-safe 2023-10-28 08:40:03.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.egg-info/zip-safe 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf.py new/img2pdf-0.6.3/src/img2pdf.py --- old/img2pdf-0.6.1/src/img2pdf.py 2025-04-27 18:54:12.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf.py 2025-11-05 21:47:24.967250800 +0100 @@ -18,6 +18,11 @@ # License along with this program. If not, see # <http://www.gnu.org/licenses/>. +""" +Lossless conversion of raster images to PDF. +""" +__version__ = "0.6.3" + import sys import os import zlib @@ -37,7 +42,6 @@ # TiffImagePlugin.DEBUG = True from PIL.ExifTags import TAGS from datetime import datetime, timezone -import jp2 from enum import Enum from io import BytesIO import logging @@ -62,7 +66,6 @@ except ImportError: have_pikepdf = False -__version__ = "0.6.1" default_dpi = 96.0 papersizes = { "letter": "8.5inx11in", @@ -434,6 +437,132 @@ pass +class jp2: + def __init__(self, data): + self.data = data + + @staticmethod + def getBox(data, byteStart, noBytes): + boxLengthValue = struct.unpack(">I", data[byteStart : byteStart + 4])[0] + boxType = data[byteStart + 4 : byteStart + 8] + contentsStartOffset = 8 + if boxLengthValue == 1: + boxLengthValue = struct.unpack(">Q", data[byteStart + 8 : byteStart + 16])[ + 0 + ] + contentsStartOffset = 16 + if boxLengthValue == 0: + boxLengthValue = noBytes - byteStart + byteEnd = byteStart + boxLengthValue + boxContents = data[byteStart + contentsStartOffset : byteEnd] + return (boxLengthValue, boxType, byteEnd, boxContents) + + @staticmethod + def parse_ihdr(data): + height, width, channels, bpp = struct.unpack(">IIHB", data[:11]) + return width, height, channels, bpp + 1 + + @staticmethod + def parse_colr(data): + meth = struct.unpack(">B", data[0:1])[0] + if meth != 1: + raise Exception("only enumerated color method supported") + enumCS = struct.unpack(">I", data[3:])[0] + if enumCS == 16: + return "RGB" + elif enumCS == 17: + return "L" + else: + raise Exception( + "only sRGB and greyscale color space is supported, " "got %d" % enumCS + ) + + @staticmethod + def parse_resc(data): + hnum, hden, vnum, vden, hexp, vexp = struct.unpack(">HHHHBB", data) + hdpi = ((hnum / hden) * (10**hexp) * 100) / 2.54 + vdpi = ((vnum / vden) * (10**vexp) * 100) / 2.54 + return hdpi, vdpi + + @staticmethod + def parse_res(data): + hdpi, vdpi = None, None + noBytes = len(data) + byteStart = 0 + boxLengthValue = 1 # dummy value for while loop condition + while byteStart < noBytes and boxLengthValue != 0: + boxLengthValue, boxType, byteEnd, boxContents = jp2.getBox( + data, byteStart, noBytes + ) + if boxType == b"resc": + hdpi, vdpi = jp2.parse_resc(boxContents) + break + return hdpi, vdpi + + @staticmethod + def parse_jp2h(data): + width, height, colorspace, hdpi, vdpi = None, None, None, None, None + noBytes = len(data) + byteStart = 0 + boxLengthValue = 1 # dummy value for while loop condition + while byteStart < noBytes and boxLengthValue != 0: + boxLengthValue, boxType, byteEnd, boxContents = jp2.getBox( + data, byteStart, noBytes + ) + if boxType == b"ihdr": + width, height, channels, bpp = jp2.parse_ihdr(boxContents) + elif boxType == b"colr": + colorspace = jp2.parse_colr(boxContents) + elif boxType == b"res ": + hdpi, vdpi = jp2.parse_res(boxContents) + byteStart = byteEnd + return (width, height, colorspace, hdpi, vdpi, channels, bpp) + + def parsejp2(self): + noBytes = len(self.data) + byteStart = 0 + boxLengthValue = 1 # dummy value for while loop condition + width, height, colorspace, hdpi, vdpi = None, None, None, None, None + while byteStart < noBytes and boxLengthValue != 0: + boxLengthValue, boxType, byteEnd, boxContents = jp2.getBox( + self.data, byteStart, noBytes + ) + if boxType == b"jp2h": + width, height, colorspace, hdpi, vdpi, channels, bpp = jp2.parse_jp2h( + boxContents + ) + break + byteStart = byteEnd + if not width: + raise Exception("no width in jp2 header") + if not height: + raise Exception("no height in jp2 header") + if not colorspace: + raise Exception("no colorspace in jp2 header") + # retrieving the dpi is optional so we do not error out if not present + return (width, height, colorspace, hdpi, vdpi, channels, bpp) + + def parsej2k(self): + lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack( + ">HHIIIIIIIIH", self.data[4:42] + ) + ssiz = [None] * csiz + xrsiz = [None] * csiz + yrsiz = [None] * csiz + for i in range(csiz): + ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack( + "BBB", self.data[42 + 3 * i : 42 + 3 * (i + 1)] + ) + assert ssiz == [7, 7, 7] + return xsiz - xosiz, ysiz - yosiz, None, None, None, csiz, 8 + + def parse(self): + if self.data[:4] == b"\xff\x4f\xff\x51": + return self.parsej2k() + else: + return self.parsejp2() + + # temporary change the attribute of an object using a context manager class temp_attr: def __init__(self, obj, field, value): @@ -768,19 +897,27 @@ <?xpacket end='w'?> """ % ( - b" pdf:Producer='%s'" % producer.encode("ascii") - if producer is not None - else b"", - b"" - if creationdate is None and nodate - else b"<xmp:ModifyDate>%s</xmp:ModifyDate>" - % datetime_to_xmpdate(now if creationdate is None else creationdate).encode( - "ascii" + ( + b" pdf:Producer='%s'" % producer.encode("ascii") + if producer is not None + else b"" + ), + ( + b"" + if creationdate is None and nodate + else b"<xmp:ModifyDate>%s</xmp:ModifyDate>" + % datetime_to_xmpdate( + now if creationdate is None else creationdate + ).encode("ascii") + ), + ( + b"" + if moddate is None and nodate + else b"<xmp:CreateDate>%s</xmp:CreateDate>" + % datetime_to_xmpdate(now if moddate is None else moddate).encode( + "ascii" + ) ), - b"" - if moddate is None and nodate - else b"<xmp:CreateDate>%s</xmp:CreateDate>" - % datetime_to_xmpdate(now if moddate is None else moddate).encode("ascii"), ) if engine != Engine.pikepdf: @@ -877,14 +1014,16 @@ PdfName.Indexed, PdfName.DeviceRGB, (len(palette) // 3) - 1, - bytes(palette) - if self.engine == Engine.pikepdf - else PdfString.encode( - [ - int.from_bytes(palette[i : i + 3], "big") - for i in range(0, len(palette), 3) - ], - hextype=True, + ( + bytes(palette) + if self.engine == Engine.pikepdf + else PdfString.encode( + [ + int.from_bytes(palette[i : i + 3], "big") + for i in range(0, len(palette), 3) + ], + hextype=True, + ) ), ] else: @@ -1309,7 +1448,12 @@ if ndpi is None: # the PNG plugin of PIL adds the undocumented "aspect" field instead of # the "dpi" field if the PNG pHYs chunk unit is not set to meters - if imgformat == ImageFormat.PNG and imgdata.info.get("aspect") is not None: + if ( + imgformat == ImageFormat.PNG + and imgdata.info.get("aspect") is not None + and imgdata.info["aspect"][0] != 0 + and imgdata.info["aspect"][1] != 0 + ): aspect = imgdata.info["aspect"] # make sure not to go below the default dpi if aspect[0] > aspect[1]: @@ -1350,7 +1494,7 @@ if imgformat == ImageFormat.JPEG2000 and rawdata is not None and imgdata is None: # this codepath gets called if the PIL installation is not able to # handle JPEG2000 files - imgwidthpx, imgheightpx, ics, hdpi, vdpi, channels, bpp = jp2.parse(rawdata) + imgwidthpx, imgheightpx, ics, hdpi, vdpi, channels, bpp = jp2(rawdata).parse() if hdpi is None: hdpi = default_dpi @@ -1402,6 +1546,20 @@ raise AlphaChannelError( "Refusing to work with multiple >8bit channels." ) + elif ( + imgformat == ImageFormat.TIFF + and imgdata is not None + and (ics in ["RGBA", "LA"] or "transparency" in imgdata.info) + ): + depth = max(imgdata.tag_v2.get(TiffImagePlugin.BITSPERSAMPLE, [1])) + if depth > 8: + logger.warning("Image with transparency and a bit depth of %d." % depth) + logger.warning("This is unsupported due to PIL limitations.") + logger.warning( + "If you accept a lossy conversion, you can manually convert " + "your images to 8 bit using `convert -depth 8` from imagemagick" + ) + raise AlphaChannelError("Refusing to work with multiple >8bit channels.") elif ics in ["LA", "PA", "RGBA"] or ( imgdata is not None and "transparency" in imgdata.info ): @@ -1891,7 +2049,7 @@ imgdata = Image.open(im) except IOError as e: # test if it is a jpeg2000 image - if rawdata[:12] == b"\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A": + if rawdata[:12] == b"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a": # image is jpeg2000 imgformat = ImageFormat.JPEG2000 elif rawdata[:8] == b"\x97\x4a\x42\x32\x0d\x0a\x1a\x0a": @@ -1982,7 +2140,7 @@ cleanup() depth = 8 if imgformat == ImageFormat.JPEG2000: - *_, depth = jp2.parse(rawdata) + *_, depth = jp2(rawdata).parse() return [ ( color, @@ -3544,9 +3702,9 @@ author=args["author"].get() if args["author"].get() else None, creator=args["creator"].get() if args["creator"].get() else None, producer=args["producer"].get() if args["producer"].get() else None, - creationdate=args["creationdate"].get() - if args["creationdate"].get() - else None, + creationdate=( + args["creationdate"].get() if args["creationdate"].get() else None + ), moddate=args["moddate"].get() if args["moddate"].get() else None, subject=args["subject"].get() if args["subject"].get() else None, keywords=args["keywords"].get() if args["keywords"].get() else None, @@ -3554,9 +3712,11 @@ nodate=args["nodate"].get(), layout_fun=layout_fun, viewer_panes=viewer_panesarg, - viewer_initial_page=args["viewer_initial_page"].get() - if args["viewer_initial_page"].get() > 1 - else None, + viewer_initial_page=( + args["viewer_initial_page"].get() + if args["viewer_initial_page"].get() > 1 + else None + ), viewer_magnification=viewer_magnificationarg, viewer_page_layout=viewer_page_layoutarg, viewer_fit_window=(args["viewer_fit_window"].get() or None), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/img2pdf_test.py new/img2pdf-0.6.3/src/img2pdf_test.py --- old/img2pdf-0.6.1/src/img2pdf_test.py 2025-04-27 17:01:31.000000000 +0200 +++ new/img2pdf-0.6.3/src/img2pdf_test.py 2025-11-05 10:22:38.958316600 +0100 @@ -276,7 +276,7 @@ def write_png(data, path, bitdepth, colortype, palette=None, iccp=None): with open(str(path), "wb") as f: - f.write(b"\x89PNG\r\n\x1A\n") + f.write(b"\x89PNG\r\n\x1a\n") # PNG image type Colour type Allowed bit depths # Greyscale 0 1, 2, 4, 8, 16 # Truecolour 2 8, 16 @@ -467,6 +467,22 @@ def compare_pdfimages_tiff(tmpdir, img, pdf): subprocess.check_call(["pdfimages", "-tiff", str(pdf), str(tmpdir / "images")]) + if os.path.isfile(tmpdir / "images-001.tif"): + # if images-001.tif exists, then it should be the smask for transparency + subprocess.check_call( + CONVERT + + [ + str(tmpdir / "images-000.tif"), + str(tmpdir / "images-001.tif"), + "-compose", + "copy-opacity", + "-composite", + str(tmpdir / "composite.tif"), + ] + ) + (tmpdir / "images-000.tif").unlink() + (tmpdir / "images-001.tif").unlink() + os.rename(tmpdir / "composite.tif", tmpdir / "images-000.tif") subprocess.check_call( COMPARE + [ @@ -484,6 +500,7 @@ subprocess.check_call(["pdfimages", "-png", str(pdf), str(tmpdir / "images")]) # images-001.png is the grayscale SMask image (the original alpha channel) if os.path.isfile(tmpdir / "images-001.png"): + # if images-001.png exists, then it should be the smask for transparency subprocess.check_call( CONVERT + [ @@ -1061,7 +1078,11 @@ "x": 0, "y": 0, }, str(identify) - assert "resolution" not in identify[0]["image"] + # Starting with imagemagick commit d9890211607c7a9eee8fd0a4dbc533ceab10ea46 + # the resolution field will always be populated + assert "resolution" not in identify[0]["image"] or identify[0]["image"][ + "resolution" + ] == {"x": 1, "y": 1}, identify[0]["image"]["resolution"] assert identify[0]["image"].get("units") == "Undefined", str(identify) assert identify[0]["image"].get("type") == "TrueColor", str(identify) endian = "endianess" if identify[0].get("version", "0") < "1.0" else "endianness" @@ -4873,6 +4894,45 @@ @pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def tiff_rgba8_pdf(tmp_path_factory, tiff_rgba8_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_rgba8_pdf") / "out.pdf" + subprocess.check_call( + [ + img2pdfprog, + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(tiff_rgba8_img), + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + assert p.pages[0].Resources.XObject.Im0.SMask is not None + + assert p.pages[0].Resources.XObject.Im0.SMask.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.SMask.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.SMask.Height == 60 + assert p.pages[0].Resources.XObject.Im0.SMask.Width == 60 + return out_pdf + + [email protected](scope="session", params=["internal", "pikepdf"]) def tiff_gray1_pdf(tmp_path_factory, tiff_gray1_img, request): out_pdf = tmp_path_factory.mktemp("tiff_gray1_pdf") / "out.pdf" subprocess.check_call( @@ -6002,23 +6062,12 @@ sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) [email protected]("engine", ["internal", "pikepdf"]) -def test_tiff_rgba8(tmp_path_factory, tiff_rgba8_img, engine): - out_pdf = tmp_path_factory.mktemp("tiff_rgba8") / "out.pdf" - assert ( - 0 - != subprocess.run( - [ - img2pdfprog, - "--producer=", - "--nodate", - "--engine=" + engine, - "--output=" + str(out_pdf), - str(tiff_rgba8_img), - ] - ).returncode - ) - out_pdf.unlink() +def test_tiff_rgba8(tmp_path_factory, tiff_rgba8_img, tiff_rgba8_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_rgba8") + # compare_ghostscript(tmpdir, tiff_rgba8_img, tiff_rgba8_pdf, gsdevice="tiff24nc") + # compare_poppler(tmpdir, tiff_rgba8_img, tiff_rgba8_pdf) + # compare_mupdf(tmpdir, tiff_rgba8_img, tiff_rgba8_pdf) + compare_pdfimages_tiff(tmpdir, tiff_rgba8_img, tiff_rgba8_pdf) @pytest.mark.skipif( @@ -6028,6 +6077,7 @@ @pytest.mark.parametrize("engine", ["internal", "pikepdf"]) def test_tiff_rgba16(tmp_path_factory, tiff_rgba16_img, engine): out_pdf = tmp_path_factory.mktemp("tiff_rgba16") / "out.pdf" + # PIL is unable to preserve more than 8 bits per sample assert ( 0 != subprocess.run( @@ -6752,7 +6802,7 @@ (972, 504), (864, 432)), (poster, None, None, f_fill, 0, (97200, 50400), (151200, 50400), (97200, 50400), (100800, 50400)), - ] + ], # fmt: on ) def test_layout(layout_test_cases): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/src/jp2.py new/img2pdf-0.6.3/src/jp2.py --- old/img2pdf-0.6.1/src/jp2.py 2024-08-07 07:14:01.000000000 +0200 +++ new/img2pdf-0.6.3/src/jp2.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,153 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2013 Johannes Schauer Marin Rodrigues <j.schauer at email.de> -# -# this module is heavily based upon jpylyzer which is -# KB / National Library of the Netherlands, Open Planets Foundation -# and released under the same license conditions -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import struct - - -def getBox(data, byteStart, noBytes): - boxLengthValue = struct.unpack(">I", data[byteStart : byteStart + 4])[0] - boxType = data[byteStart + 4 : byteStart + 8] - contentsStartOffset = 8 - if boxLengthValue == 1: - boxLengthValue = struct.unpack(">Q", data[byteStart + 8 : byteStart + 16])[0] - contentsStartOffset = 16 - if boxLengthValue == 0: - boxLengthValue = noBytes - byteStart - byteEnd = byteStart + boxLengthValue - boxContents = data[byteStart + contentsStartOffset : byteEnd] - return (boxLengthValue, boxType, byteEnd, boxContents) - - -def parse_ihdr(data): - height, width, channels, bpp = struct.unpack(">IIHB", data[:11]) - return width, height, channels, bpp + 1 - - -def parse_colr(data): - meth = struct.unpack(">B", data[0:1])[0] - if meth != 1: - raise Exception("only enumerated color method supported") - enumCS = struct.unpack(">I", data[3:])[0] - if enumCS == 16: - return "RGB" - elif enumCS == 17: - return "L" - else: - raise Exception( - "only sRGB and greyscale color space is supported, " "got %d" % enumCS - ) - - -def parse_resc(data): - hnum, hden, vnum, vden, hexp, vexp = struct.unpack(">HHHHBB", data) - hdpi = ((hnum / hden) * (10**hexp) * 100) / 2.54 - vdpi = ((vnum / vden) * (10**vexp) * 100) / 2.54 - return hdpi, vdpi - - -def parse_res(data): - hdpi, vdpi = None, None - noBytes = len(data) - byteStart = 0 - boxLengthValue = 1 # dummy value for while loop condition - while byteStart < noBytes and boxLengthValue != 0: - boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes) - if boxType == b"resc": - hdpi, vdpi = parse_resc(boxContents) - break - return hdpi, vdpi - - -def parse_jp2h(data): - width, height, colorspace, hdpi, vdpi = None, None, None, None, None - noBytes = len(data) - byteStart = 0 - boxLengthValue = 1 # dummy value for while loop condition - while byteStart < noBytes and boxLengthValue != 0: - boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes) - if boxType == b"ihdr": - width, height, channels, bpp = parse_ihdr(boxContents) - elif boxType == b"colr": - colorspace = parse_colr(boxContents) - elif boxType == b"res ": - hdpi, vdpi = parse_res(boxContents) - byteStart = byteEnd - return (width, height, colorspace, hdpi, vdpi, channels, bpp) - - -def parsejp2(data): - noBytes = len(data) - byteStart = 0 - boxLengthValue = 1 # dummy value for while loop condition - width, height, colorspace, hdpi, vdpi = None, None, None, None, None - while byteStart < noBytes and boxLengthValue != 0: - boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes) - if boxType == b"jp2h": - width, height, colorspace, hdpi, vdpi, channels, bpp = parse_jp2h( - boxContents - ) - break - byteStart = byteEnd - if not width: - raise Exception("no width in jp2 header") - if not height: - raise Exception("no height in jp2 header") - if not colorspace: - raise Exception("no colorspace in jp2 header") - # retrieving the dpi is optional so we do not error out if not present - return (width, height, colorspace, hdpi, vdpi, channels, bpp) - - -def parsej2k(data): - lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack( - ">HHIIIIIIIIH", data[4:42] - ) - ssiz = [None] * csiz - xrsiz = [None] * csiz - yrsiz = [None] * csiz - for i in range(csiz): - ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack( - "BBB", data[42 + 3 * i : 42 + 3 * (i + 1)] - ) - assert ssiz == [7, 7, 7] - return xsiz - xosiz, ysiz - yosiz, None, None, None, csiz, 8 - - -def parse(data): - if data[:4] == b"\xff\x4f\xff\x51": - return parsej2k(data) - else: - return parsejp2(data) - - -if __name__ == "__main__": - import sys - - width, height, colorspace, hdpi, vdpi, channels, bpp = parse( - open(sys.argv[1], "rb").read() - ) - print("width = %d" % width) - print("height = %d" % height) - print("colorspace = %s" % colorspace) - print("hdpi = %s" % hdpi) - print("vdpi = %s" % vdpi) - print("channels = %s" % channels) - print("bpp = %s" % bpp) Binary files old/img2pdf-0.6.1/src/tests/input/mono.jb2 and new/img2pdf-0.6.3/src/tests/input/mono.jb2 differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/img2pdf-0.6.1/tox.ini new/img2pdf-0.6.3/tox.ini --- old/img2pdf-0.6.1/tox.ini 1970-01-01 01:00:00.000000000 +0100 +++ new/img2pdf-0.6.3/tox.ini 2024-01-16 19:44:19.189303600 +0100 @@ -0,0 +1,18 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py37, py38, py39, py310 +skip_missing_interpreters = true + +[testenv] +deps = + pdfrw + pytest + pikepdf + numpy + scipy +commands = + python -m pytest -vv
