Open-source project
josch/img2pdf avatar
josch/img2pdf

img2pdf: raster images into PDF without re-encoding them

mirror of https://gitlab.mister-muffin.de/josch/img2pdf for Travis and appveyor CI

580 stars70 forksPythonLGPL-3.0

At a glance

What is it?
img2pdf treats the PDF as a container and pastes the image data into it unchanged whenever the format allows, which makes the output bit for bit identical to the input, adds a few hundred bytes of overhead and avoids loading the pixels into memory at all.
Who is it for?
img2pdf is the right tool for scanned documents, archival conversions and any batch where the image data must survive the PDF unchanged, and for pipelines that feed a scanner straight into a PDF. It is the wrong tool when you need features beyond embedding, such as page descriptions, bookmarks or document metadata beyond what the converter writes, and it is the wrong tool when the input is CMYK or 1-bit monochrome and you care about output size, because those paths re-encode.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 114 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Lossless, small, fast, in that order

img2pdf converts raster images to PDF losslessly, and the readme states its priorities in a ranked list, which is unusual and useful.

The first priority is always lossless: the image embedded in the PDF will always have the exact same colour information for every pixel as the input. The second is small: where possible, the difference in file size between the input image and the output PDF will only be the overhead of the PDF container itself. The third is fast: where possible, the input image is pasted into the PDF document as it is, without any CPU heavy re-encoding of the pixel data.

The comparison the readme draws is with conventional conversion software such as ImageMagick, and it names the three ways such tools fail. They are not lossless because they re-encode to JPEG, which is lossy. They are not small because they use wasteful flate encoding of raw pixel data. And they are not fast because the input data gets re-encoded.

There is a fourth consequence that follows from the third point, and it is the one that matters most for large scans. Because the raw pixel data never has to be loaded into memory, img2pdf is able to handle much larger input than other software. That is not a benchmark claim but a structural one. A tool that re-encodes has to hold and rewrite the image, and a tool that copies has to seek around in a file it never fully reads.

So the design decision is simple and it explains everything else: the PDF is treated as a container format, and the pixel data is moved rather than transformed.

The format table: when the data is embedded directly

The readme publishes a table of which input formats can be embedded unchanged, and it is the most useful page in the documentation.

Direct embedding, meaning the image data is copied into the PDF as it is, applies to JPEG in any colour space, JPEG2000 in any colour space, PNG when it is non-interlaced and has no transparency, TIFF with CCITT Group 4 encoded data in 1-bit monochrome, and JBIG2 with single-page generic coding in 1-bit monochrome. The parenthetical notes that JBIG2 should be produced with a tool such as jbig2enc.

In those cases img2pdf only increases the file size by the size of the PDF container, typically around 500 to 700 bytes, and since the data is only copied and not re-encoded it is also typically faster than other solutions for those formats.

For everything else, the pixel data has to be transformed to become compatible with PDF. The table shows what happens in each case. Any input except CMYK and 1-bit monochrome is handled with the PNG Paeth filter applied to the pixel data. 1-bit monochrome input is handled with CCITT Group 4 instead. And only for CMYK input is no filter applied, with flate compression applied at the end.

That table is the decision rule for a pipeline. A directory of JPEGs goes through the direct path and stays bit identical. A directory of 1-bit scanned bitmaps goes through the CCITT path, which is lossless as well but is a re-encode. And CMYK input takes the flate path, which is the case where the small priority is hardest to satisfy, since flate on raw pixel data is exactly the wasteful encoding the readme criticises in other tools.

Why the input has to be a file, and the scanner pipeline

One constraint is stated early and it surprises people: the images must be provided as files, because img2pdf needs to seek in the file descriptor.

That is a direct consequence of the copying design. To embed the data without reading it into memory, the tool has to be able to move the read position around the file and tell the PDF where the bytes live. A pipe cannot be seeked, so a stream is not an acceptable input for the direct path, and the tool states the requirement rather than silently degrading to a re-encode.

The output side is more flexible. If no output file is given with the -o or --output option, the output goes to standard output, and a typical invocation looks like this.

bash
	$ img2pdf img1.png img2.jpg -o out.pdf

With no command line arguments at all, img2pdf reads a single image from standard input and writes the resulting PDF to standard output, which is what makes the scanner example work.

bash
	$ scanimage --mode=Color --resolution=300 | pnmtojpeg -quality 90 | img2pdf > scan.pdf

That pipeline is the case the design serves best. A SANE scanner produces a PPM stream, pnmtojpeg converts it to a JPEG at the quality you choose, and img2pdf wraps the result in a PDF container with no re-encode. The intermediate JPEG is a lossy step that img2pdf has no part in, and the quality argument of 90 is the one lever the readme exposes here.

The detailed documentation is available by running the tool with the help option.

Installing it on Linux, from source, and on Windows

There are three routes on a Debian or Ubuntu based system, and one for Windows.

The distribution package is the shortest.

bash
	$ apt install img2pdf

The pip route installs the same tool from the package index.

bash
	$ pip3 install img2pdf

And from a source checkout, you install the directory in place.

bash
	$ cd img2pdf/
	$ pip3 install .

The readme also shows how to test the console script without touching the system Python, which is the right approach when you are evaluating it.

bash
	$ cd img2pdf/
	$ virtualenv ve
	$ ve/bin/pip3 install .

Then the converter is exercised against a file that ships with the repository.

bash
	$ ve/bin/img2pdf -o test.pdf src/tests/test.jpg

For Windows, the readme points at the releases section of the project and names the artefact to download, img2pdf.exe, for anyone who does not want to set up Python on the machine.

The packaging is a modern one, using the flit build backend, and the runtime dependencies are only Pillow and pikepdf. The console script is registered as img2pdf, and a separate GUI entry point is registered as img2pdf-gui, which is the tool described as an experimental GUI with all settings currently disabled, based on tkinter, and working on Linux, Windows and MacOS. The readme invites merge requests for adding features to it.

Using img2pdf as a library

The package can be used from Python, and the readme lists the calling conventions, which is worth looking at because the different forms exist for a reason.

python
	# 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))

The convert function accepts a filename, an open file handle, a pathlib.Path, in-memory image data as a string, or several inputs at once, either as separate arguments or as a list. The handle form is the one that matches the seek requirement described earlier, and the in-memory form is the one that does not.

Two of the calling conventions also carry a hint about the library's own API. Converting from a filename and converting from a file handle are separate code paths, which suggests that the file path opens the input for seeking while the handle path assumes the caller has already done that. And the in-memory form is listed as a variant with a string, which is the case where the tool cannot seek and therefore cannot take the direct embedding path for every format.

For a library user, that distinction is the thing to check first, because it decides whether your output is byte-identical to the input or re-encoded.

The failure modes worth knowing before you script this

The readme has a Bugs section, and one of its entries explains the single most common reason a conversion fails.

An error is produced if the input image is broken, and the readme says this commonly happens when the input image has an invalid EXIF Orientation value of zero. Only nine values from 1 to 9 are permitted, but Android phones and Canon DSLR cameras produce JPEG images with the invalid value of zero.

There are three ways out. You can fix the input images with exiftool or similar software before passing the JPEG to img2pdf. You can run img2pdf with the rotation option set to ifvalid from the command line. Or, when using img2pdf as a library, you can pass rotation set to img2pdf.Rotation.ifvalid as an argument to convert. The last one is the form that belongs in code, and it is the reason the library exposes a rotation argument at all.

The second entry is a request rather than a bug report. 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, the author asks you to contact him. That is a compatibility question about how a viewer interprets a directly embedded stream, and it is the kind of thing that a compatibility matrix in a different project would have answered for you.

The third is a safety limit rather than a defect. img2pdf uses PIL, or Pillow, to obtain image meta data and to convert the input if necessary, and Pillow limits the maximum number of pixels an input image is allowed to have, to prevent decompression bomb denial of service attacks. If you are sure of what you are doing, the safeguard can be disabled with the --pillow-limit-break option, which allows very large input images to be processed.

That option deserves a second look in a pipeline. Turning it off removes a protection that exists because a crafted image can otherwise exhaust memory, and the fact that img2pdf copies rather than decodes does not fully cancel the risk, since Pillow is still reading metadata and may still have to convert.

The GitLab project, the GitHub mirror, and the LGPL

This repository is a mirror. The description says so, and the homepage points at the real project, a GitLab repository under the mister-muffin.de domain, with the mirror existing for Travis and AppVeyor continuous integration, which is why the top level carries a .travis.yml and an appveyor.yml alongside the badges for both services.

That has a practical consequence for anyone using the project. Issues, releases and discussions live on GitLab, the Windows executable is linked from the GitLab releases section, and the repository you clone on GitHub is a copy. A change you want to make goes to GitLab, and the mirror follows.

The licence is the GNU Lesser General Public License version 3, declared in the packaging metadata with the classifier for the LGPLv3 and the licence file referenced from the project table. The LGPL is a middle ground between a permissive licence and copyleft, and for a tool like this the practical question is whether you intend to distribute a modified version, which is where the obligations bite. This is not legal advice.

Two details in the packaging are worth flagging. The build uses flit with a version constraint on the core version, and the project metadata marks the version and description as dynamic, which means they are read from the module rather than duplicated in the manifest. The setup.py file that is also present still declares version 0.6.3, so the two packaging files are not in step, and the CHANGES.rst file at the root is the changelog.

The last push to the mirror was on 2026-07-01, the repository is not archived, and the project publishes no GitHub releases, so the GitLab releases page is where versions are announced.

Editorial conclusion

img2pdf is the right tool for scanned documents, archival conversions and any batch where the image data must survive the PDF unchanged, and for pipelines that feed a scanner straight into a PDF. It is the wrong tool when you need features beyond embedding, such as page descriptions, bookmarks or document metadata beyond what the converter writes, and it is the wrong tool when the input is CMYK or 1-bit monochrome and you care about output size, because those paths re-encode. Before adopting it in a pipeline, fix the EXIF orientation problem in your inputs, since a zero orientation value is the most common cause of a failed conversion, and decide whether the Pillow pixel limit needs to be lifted with the option the documentation names.

Frequently asked questions

What does img2pdf do differently from ImageMagick?

It embeds the image data in the PDF without re-encoding it whenever the format allows, so the output is lossless, adds only the PDF container overhead of roughly 500 to 700 bytes, and avoids the CPU cost of re-encoding. The readme argues that conventional tools fail on all three counts, and that because the raw pixel data is never loaded into memory, img2pdf can also handle much larger inputs.

Which image formats does img2pdf embed without re-encoding?

JPEG and JPEG2000 in any colour space, non-interlaced PNG without transparency, TIFF with CCITT Group 4 encoded data, and JBIG2 with single-page generic coding. Other input is transformed, with the PNG Paeth filter applied by default, CCITT Group 4 for 1-bit monochrome, and flate compression for CMYK.

Why does img2pdf require the input to be a file?

The readme states that the images must be provided as files because img2pdf needs to seek in the file descriptor. Output, by contrast, can go to standard output when no output file is given, which is what makes the scanimage and pnmtojpeg pipeline possible.

How do I install img2pdf on Debian, Ubuntu or Windows?

On Debian and Ubuntu based systems you can install it from the official repositories with apt install img2pdf, or with pip3 install img2pdf, or from a source checkout with pip3 install . The readme also shows how to test it in a virtualenv. On Windows, the readme points to the releases section for the img2pdf.exe executable.

What are the common conversion failures in img2pdf?

The most common one is a broken input image caused by an invalid EXIF Orientation value of zero, which Android phones and Canon DSLR cameras produce. The fixes given are to repair the images with exiftool, to run img2pdf with the rotation option set to ifvalid, or to pass rotation set to img2pdf.Rotation.ifvalid to convert when using the library.

Official sources

  1. Issues
  2. josch/img2pdf on GitHub
  3. License: LGPL-3.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/josch-img2pdf.svg)](https://hysenlabs.com/projects/josch-img2pdf)