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
 
 [![Travis 
Status](https://travis-ci.com/josch/img2pdf.svg?branch=main)](https://app.travis-ci.com/josch/img2pdf)
 [![Appveyor 
Status](https://ci.appveyor.com/api/projects/status/2kws3wkqvi526llj/branch/main?svg=true)](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
-
-[![Travis 
Status](https://travis-ci.com/josch/img2pdf.svg?branch=main)](https://app.travis-ci.com/josch/img2pdf)
-[![Appveyor 
Status](https://ci.appveyor.com/api/projects/status/2kws3wkqvi526llj/branch/main?svg=true)](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.
-
-![](screenshot.png)
-
-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

Reply via email to