Compare commits
304 commits
Author | SHA1 | Date | |
---|---|---|---|
bb188a3eaf | |||
69c3ac6b25 | |||
dffc0dbe16 | |||
|
b91007fef8 | ||
a8cb28ba31 | |||
c6d12d6239 | |||
59132f20f8 | |||
3ba7d17e15 | |||
43c16ac369 | |||
08c4d9beec | |||
9e6eba9f40 | |||
5aeb628506 | |||
b6dbfdb481 | |||
23436114f8 | |||
2d5e4e3cb7 | |||
5e515abb6f | |||
a2e2998fb1 | |||
14948e7ba8 | |||
bcfdf8b54e | |||
9f74740c95 | |||
cbc3d50c63 | |||
4b549592bf | |||
5540365cfd | |||
819b366bf5 | |||
cc8c708295 | |||
fb9537d8b7 | |||
7678435eb7 | |||
ba7a360866 | |||
7f0bf47ff3 | |||
|
5cd0918d50 | ||
|
f157ced05d | ||
09064e8e70 | |||
2f736d7891 | |||
e05580a49a | |||
acc25a4926 | |||
f597887088 | |||
3e832fbcc2 | |||
1e8557cef1 | |||
29921eeabd | |||
33139612f8 | |||
64d27f4a8b | |||
85cbe1d128 | |||
b25429a4c1 | |||
c703e9df06 | |||
79e9985f35 | |||
cb2644c34f | |||
81502f21af | |||
0cbcb8fa12 | |||
e9e04b6dd9 | |||
fc059ee471 | |||
25466113e9 | |||
7405635b72 | |||
aea472101b | |||
7fa67bb337 | |||
7d40569aa1 | |||
83f9c32328 | |||
be8369373f | |||
10c6901fa3 | |||
57d7e07e6b | |||
272fe0433f | |||
ef7b9e739d | |||
af6fe27d53 | |||
bad6fcae39 | |||
d9b90499f3 | |||
edb0d29a14 | |||
bb3e8b0098 | |||
f454ebc6a6 | |||
c3db273e23 | |||
87afabd3cf | |||
|
5045282cc2 | ||
fb4b96452a | |||
c553e169a4 | |||
d9345ac767 | |||
1d52530229 | |||
3b117e674b | |||
e8ca53738f | |||
7c48bfb868 | |||
244f034a2e | |||
3da370d3bd | |||
6cff2931e4 | |||
6a55258804 | |||
3cdeab08ab | |||
cea7c9120b | |||
9eacfdaa76 | |||
95a313f437 | |||
30d705f020 | |||
dc926b2cf2 | |||
a8fdbd0038 | |||
6ff175d637 | |||
0732dff0be | |||
50b7145f64 | |||
e522ec14d9 | |||
9c9e5ece19 | |||
354fd7c264 | |||
392d4a665e | |||
09ad147d97 | |||
80393b6efa | |||
e265738ac2 | |||
1ffb160453 | |||
cde7472d15 | |||
6eec05c11c | |||
|
f483638b17 | ||
|
7f216a8848 | ||
|
2476215f39 | ||
|
f62858c245 | ||
|
a5e4da5755 | ||
|
64db7909ec | ||
|
af5ae5b9b6 | ||
d03f331521 | |||
635b08c321 | |||
152f6fb629 | |||
1f3b456ac9 | |||
4c5b72dab0 | |||
853a1ec363 | |||
55d589a548 | |||
5c617965f5 | |||
0067edf965 | |||
91e3a94c3d | |||
3d7e0e6812 | |||
b4c8aa1a5f | |||
114d7270a2 | |||
80d24a1d49 | |||
ea2245757f | |||
9cda595cd5 | |||
2eabebb513 | |||
02c85a50ad | |||
c97ce34023 | |||
81325d3998 | |||
8d2ae0f64e | |||
d29c596fe7 | |||
cd1088a5a9 | |||
2a8779295f | |||
6cd819d28f | |||
c48e1dbb1e | |||
d08d8c5be9 | |||
0e4f0047b2 | |||
0ce25d08c2 | |||
c5fd43e851 | |||
17fd73aed8 | |||
454d4e7775 | |||
cb2243fd10 | |||
129bd15b43 | |||
b8bfa98218 | |||
b5f0912e13 | |||
213a6af41f | |||
9290cb4a10 | |||
|
505344f83e | ||
|
32b4ed1f43 | ||
b2c3b641db | |||
c4fb1d886f | |||
11907242a5 | |||
692b54ac67 | |||
cc79581e2c | |||
c7db805dee | |||
f0b57985ee | |||
1ba02bf838 | |||
042aac71eb | |||
7da0a00ef3 | |||
67dca425d1 | |||
86fddab622 | |||
c229e20547 | |||
a53fed5d17 | |||
788102ee05 | |||
d92790ad26 | |||
f0e7e8daaf | |||
9bd41dba1e | |||
6c742be642 | |||
7ccd987d6d | |||
c506cf0b8d | |||
fbb51d9083 | |||
593aeb5d2d | |||
c62ed4d691 | |||
2493af173c | |||
a39beb5c5e | |||
7a72c38c3d | |||
1d5be0cc9d | |||
e151ca27eb | |||
7aed0ea0d0 | |||
bed0b4cdae | |||
53e991bec2 | |||
11f7db5003 | |||
000fc3b5ac | |||
78bd869b10 | |||
0992832ab0 | |||
1d0e4c5272 | |||
bfd822b74e | |||
2c8e417c51 | |||
66fd7cc765 | |||
5b7f93bb9a | |||
a2d846052e | |||
a84a1b8480 | |||
663010ca61 | |||
802dd4b1f3 | |||
93f65a49c9 | |||
bca3f802ac | |||
65d9aed630 | |||
791c9497ed | |||
1cd2674a2c | |||
f4b296cef3 | |||
5c7ffb09a1 | |||
7b58c4e58d | |||
c6d04acc4b | |||
c49a098e7b | |||
e4dece1c9f | |||
997fe8efd8 | |||
c808061b4b | |||
17dd59e722 | |||
60fa588cfb | |||
559d42cd4a | |||
6da2bc3aee | |||
9d184ad0cd | |||
082f999ac8 | |||
ceba6a8223 | |||
9449f96345 | |||
0bbbc7a31a | |||
a270c987f0 | |||
f0e4c6188e | |||
fb2916e6b2 | |||
0521426e57 | |||
cbbddf7fe0 | |||
7a3daec620 | |||
1bb21f0c85 | |||
24b679d8e5 | |||
1b6e9e3da6 | |||
17624991ee | |||
5002fb1068 | |||
5375a17181 | |||
b991516714 | |||
b31d4e48d8 | |||
be9e48871c | |||
f1e1dab850 | |||
c17fd0011d | |||
2b26daeb47 | |||
ed6187d541 | |||
82706671cc | |||
01022487b7 | |||
07903e9ef1 | |||
d1f101c36a | |||
8d7996709a | |||
e04f7c0a26 | |||
462e1c23d5 | |||
1a8f3f436b | |||
|
a282692ac1 | ||
|
2faeb2005d | ||
0639dbd47c | |||
78183c642b | |||
b39e755424 | |||
8bccc02c67 | |||
|
4cebd9f15d | ||
68ca35f39e | |||
42f8ac54a8 | |||
d4700dbf38 | |||
bd55ac5a45 | |||
2f3394a1cb | |||
50a3109c43 | |||
03bdc33053 | |||
b758b54ce8 | |||
a2b969640a | |||
8961c78dbf | |||
6c44d8cea6 | |||
c0ed810036 | |||
ccca845606 | |||
53685934f9 | |||
ba5a9a1dfc | |||
a628ed22f9 | |||
fbcaca5e6b | |||
a9f4c9b665 | |||
002c9c4466 | |||
198c98a5f9 | |||
9395b6fbbe | |||
f5d8d86dff | |||
36c5034db5 | |||
|
7131b3d6ee | ||
b3fb2de5f6 | |||
4c5faf408f | |||
2ad0c036b2 | |||
1aa71e3746 | |||
75c43de09d | |||
eec1a25a92 | |||
7de174f4b5 | |||
48d5b4d7af | |||
ce68cb21d7 | |||
d931f02709 | |||
b99fae1380 | |||
ac9ba1d0f6 | |||
eaf9de23c9 | |||
2a33ff275d | |||
ebd8d911bb | |||
e7755f0a87 | |||
47a1e0c81e | |||
3142824bab | |||
84ce9bbd9c | |||
90293204b2 | |||
d09aa76c9e | |||
ade5768d72 | |||
920506b867 | |||
e78dd80451 | |||
d9a6c9db03 | |||
7244d2c6ed | |||
1d9a25dfd2 | |||
9836b976d3 | |||
|
a8269391e9 | ||
|
b54617de19 | ||
|
0e76a5bd97 |
31 changed files with 11494 additions and 1456 deletions
3
.mailmap
Normal file
3
.mailmap
Normal file
|
@ -0,0 +1,3 @@
|
|||
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
|
||||
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> <j.schauer@email.de>
|
||||
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> <josch@pyneo.org>
|
42
.travis.yml
Normal file
42
.travis.yml
Normal file
|
@ -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
|
164
CHANGES.rst
164
CHANGES.rst
|
@ -2,34 +2,158 @@
|
|||
CHANGES
|
||||
=======
|
||||
|
||||
0.2.4
|
||||
-----
|
||||
0.6.1 (2025-04-27)
|
||||
------------------
|
||||
|
||||
- testsuite fixes
|
||||
|
||||
0.6.0 (2025-02-15)
|
||||
------------------
|
||||
|
||||
- Add support for JBIG2 (generic coding)
|
||||
- Add convert_to_docobject() broken out from convert()
|
||||
- Add pil_get_dpi() broken out from get_imgmetadata()
|
||||
|
||||
0.5.1 (2023-11-26)
|
||||
------------------
|
||||
|
||||
- no default ICC profile location for PDF/A-1b on Windows
|
||||
- workaround for PNG input without dpi units but non-square dpi aspect ratio
|
||||
|
||||
0.5.0 (2023-10-28)
|
||||
------------------
|
||||
|
||||
- support MIFF for 16 bit CMYK input
|
||||
- accept pathlib.Path objects as input
|
||||
- don't store RGB ICC profiles from bilevel or grayscale TIFF, PNG and JPEG
|
||||
- thumbnails are no longer included by default and --include-thumbnails has to
|
||||
be used if you want them
|
||||
- support for pikepdf (>= 6.2.0)
|
||||
|
||||
0.4.4 (2022-04-07)
|
||||
------------------
|
||||
|
||||
- --viewer-page-layout support for twopageright and twopageleft
|
||||
- Add B and JB paper sizes
|
||||
- support for pikepdf (>= 5.0.0) and Pillow (>= 9.1.0)
|
||||
|
||||
0.4.3 (2021-10-24)
|
||||
------------------
|
||||
|
||||
- fix --viewer-initial-page (broken in last release)
|
||||
|
||||
0.4.2 (2021-10-11)
|
||||
------------------
|
||||
|
||||
- add --rotation
|
||||
- allow palette PNG images with ICC profile
|
||||
- sort globbing result on windows
|
||||
- convert 8-bit PNG alpha channels to /SMasks in PDF
|
||||
- remove pdfrw from tests
|
||||
|
||||
0.4.1 (2021-05-09)
|
||||
------------------
|
||||
|
||||
- support wildcards in paths on windows
|
||||
- support MPO images
|
||||
- fix page border computation
|
||||
- use "img2pdf" logger instead of "root" logger
|
||||
- add --from-file
|
||||
|
||||
0.4.0 (2020-08-07)
|
||||
------------------
|
||||
|
||||
- replace --without-pdfrw by --engine=internal or --engine=pdfrw
|
||||
- add pikepdf as additional rendering engine and add --engine=pikepdf
|
||||
- support for creating PDF/A-1b compliant PDF using the --pdfa option
|
||||
(this also requires the presence of an ICC profile somewhere on the system)
|
||||
- support for images with embedded ICC profile as input
|
||||
- rewrite tests
|
||||
* use pytest via tox
|
||||
* use pikepdf instead of pdfrw
|
||||
* use imagemagick json output instead of identify -verbose
|
||||
- format all code with black
|
||||
|
||||
0.3.6 (2020-04-30)
|
||||
------------------
|
||||
|
||||
- fix tests for Fedora on arm64
|
||||
|
||||
0.3.5 (2020-04-28)
|
||||
------------------
|
||||
|
||||
- remove all Python 2 support
|
||||
- disable pdfrw by default
|
||||
|
||||
0.3.4 (2020-04-05)
|
||||
------------------
|
||||
|
||||
- test.sh: replace imagemagick with custom python script to produce bit-by-bit
|
||||
identical results on all architectures
|
||||
- add --crop-border, --bleed-border, --trim-border and --art-border options
|
||||
- first draft of a rudimentary tkinter gui (run with --gui)
|
||||
|
||||
0.3.3 (2019-01-07)
|
||||
------------------
|
||||
|
||||
- restore basic support for Python 2
|
||||
- also ship test.sh
|
||||
- add legal and tabloid paper formats
|
||||
- respect exif rotation tag
|
||||
|
||||
0.3.2 (2018-11-20)
|
||||
------------------
|
||||
|
||||
- support big endian TIFF with lsb-to-msb FillOrder
|
||||
- support multipage CCITT Group 4 TIFF
|
||||
- also reject palette images with transparency
|
||||
- support PNG images with 1, 2, 4 or 16 bits per sample
|
||||
- support multipage TIFF with differently encoded images
|
||||
- support CCITT Group4 TIFF without rows-per-strip
|
||||
- add extensive test suite
|
||||
|
||||
0.3.1 (2018-08-04)
|
||||
------------------
|
||||
|
||||
- Directly copy data from CCITT Group 4 encoded TIFF images into the PDF
|
||||
container without re-encoding
|
||||
|
||||
0.3.0 (2018-06-18)
|
||||
------------------
|
||||
|
||||
- Store non-jpeg images using PNG compression
|
||||
- Support arbitrarily large pages via PDF /UserUnit field
|
||||
- Disallow input with alpha channel as it cannot be preserved
|
||||
- Add option --pillow-limit-break to support very large input
|
||||
|
||||
0.2.4 (2017-05-23)
|
||||
------------------
|
||||
|
||||
- Restore support for Python 2.7
|
||||
- Add support for PyPy
|
||||
- Add support for testing using tox
|
||||
|
||||
0.2.3
|
||||
-----
|
||||
0.2.3 (2017-01-20)
|
||||
------------------
|
||||
|
||||
- version number bump for botched pypi upload...
|
||||
|
||||
0.2.2
|
||||
-----
|
||||
0.2.2 (2017-01-20)
|
||||
------------------
|
||||
|
||||
- automatic monochrome CCITT Group4 encoding via Pillow/libtiff
|
||||
|
||||
0.2.1
|
||||
-----
|
||||
0.2.1 (2016-05-04)
|
||||
------------------
|
||||
|
||||
- set img2pdf as /producer value
|
||||
- support multi-frame images like multipage TIFF and animated GIF
|
||||
- support for palette images like GIF
|
||||
- support all colorspaces and imageformats knows by PIL
|
||||
- support all colorspaces and imageformats known by PIL
|
||||
- read horizontal and vertical dpi from JPEG2000 files
|
||||
|
||||
0.2.0
|
||||
-----
|
||||
0.2.0 (2015-05-10)
|
||||
------------------
|
||||
|
||||
- now Python3 only
|
||||
- pep8 compliant code
|
||||
|
@ -64,34 +188,34 @@ CHANGES
|
|||
- explicitly store date in UTC and allow parsing all date formats understood
|
||||
by dateutil and `date --date`
|
||||
|
||||
0.1.5
|
||||
-----
|
||||
0.1.5 (2015-02-16)
|
||||
------------------
|
||||
|
||||
- Enable support for CMYK images
|
||||
- Rework test suite
|
||||
- support file objects as input
|
||||
|
||||
0.1.4
|
||||
-----
|
||||
0.1.4 (2015-01-21)
|
||||
------------------
|
||||
|
||||
- add Python 3 support
|
||||
- make output reproducible by sorting and --nodate option
|
||||
|
||||
0.1.3
|
||||
-----
|
||||
0.1.3 (2014-11-10)
|
||||
------------------
|
||||
|
||||
- Avoid leaking file descriptors
|
||||
- Convert unrecognized colorspaces to RGB
|
||||
|
||||
0.1.1
|
||||
-----
|
||||
0.1.1 (2014-09-07)
|
||||
------------------
|
||||
|
||||
- allow running src/img2pdf.py standalone
|
||||
- license change from GPL to LGPL
|
||||
- Add pillow 2.4.0 support
|
||||
- add options to specify pdf dimensions in points
|
||||
|
||||
0.1.0 (unreleased)
|
||||
0.1.0 (2014-03-14, unreleased)
|
||||
------------------
|
||||
|
||||
- Initial PyPI release.
|
||||
|
|
83
HACKING
Normal file
83
HACKING
Normal file
|
@ -0,0 +1,83 @@
|
|||
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:
|
||||
|
||||
$ rm -rf dist/*
|
||||
$ python3 setup.py sdist
|
||||
$ twine upload dist/*
|
||||
|
||||
- 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
|
||||
---------------------
|
||||
|
||||
$ git clean -fdx && git reset --hard
|
||||
$ ./configure --prefix=$(pwd)/prefix
|
||||
$ make -j$(nproc)
|
||||
$ make install
|
||||
$ LD_LIBRARY_PATH=$(pwd)/prefix/lib prefix/bin/compare ...
|
165
LICENSE
Normal file
165
LICENSE
Normal file
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
|
@ -1,6 +1,9 @@
|
|||
include README.md
|
||||
include test_comp.sh
|
||||
include test.sh
|
||||
include magick.py
|
||||
include CHANGES.rst
|
||||
include LICENSE
|
||||
recursive-include src *.jpg
|
||||
recursive-include src *.pdf
|
||||
recursive-include src *.png
|
||||
|
|
326
README.md
326
README.md
|
@ -1,70 +1,56 @@
|
|||
[](https://app.travis-ci.com/josch/img2pdf)
|
||||
[](https://ci.appveyor.com/project/josch/img2pdf/branch/main)
|
||||
|
||||
img2pdf
|
||||
=======
|
||||
|
||||
Losslessly convert raster images to PDF. The file size will not unnecessarily
|
||||
increase. It can for example be used to create a PDF document from a number of
|
||||
scans that are only available in JPEG format. Existing solutions would either
|
||||
re-encode the input JPEG files (leading to quality loss) or store them in the
|
||||
zip/flate format which results into the PDF becoming unnecessarily large in
|
||||
terms of its file size.
|
||||
Lossless conversion of raster images to PDF. You should use img2pdf if your
|
||||
priorities are (in this order):
|
||||
|
||||
Background
|
||||
----------
|
||||
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
|
||||
|
||||
Quality loss can be avoided when converting JPEG and JPEG2000 images to PDF by
|
||||
embedding them into the PDF without re-encoding them. This is what img2pdf
|
||||
does. It thus treats the PDF format merely as a container format for storing
|
||||
one or more JPEGs without re-encoding the JPEG images themselves.
|
||||
Conventional conversion software (like ImageMagick) would either:
|
||||
|
||||
If you know an existing tool which allows one to embed JPEG and JPEG2000 images
|
||||
into a PDF container without recompression, please contact me so that I can put
|
||||
this code into the garbage bin.
|
||||
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
|
||||
|
||||
Functionality
|
||||
-------------
|
||||
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.
|
||||
|
||||
This program will take a list of raster images and produce a PDF file with the
|
||||
images embedded in it. JPEG and JPEG2000 images will be included without
|
||||
recompression and the resulting PDF will only be slightly larger than the input
|
||||
images due to the overhead of the PDF container. Raster images in other
|
||||
formats (like png, gif or tif) will be included using the lossless zip/flate
|
||||
encoding which usually leads to a significant increase in the PDF size if the
|
||||
input was for example a png image. This is unfortunately unavoidable because
|
||||
there is no other way to store arbitrary RGB bitmaps in PDF in a lossless way
|
||||
other than zip/flate encoding. And zip/flate compresses bitmaps worse than png
|
||||
is able to compress them.
|
||||
The following table shows how img2pdf handles different input depending on the
|
||||
input file format and image color space.
|
||||
|
||||
As a result, this tool is able to losslessly wrap raster images into a PDF
|
||||
container with a quality to filesize ratio that is typically better (in case of
|
||||
JPEG and JPEG2000 images) or equal (in case of other formats) than that of
|
||||
existing tools.
|
||||
| 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 example, imagemagick will re-encode the input JPEG image (thus changing
|
||||
its content):
|
||||
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.
|
||||
|
||||
$ convert img.jpg img.pdf
|
||||
$ pdfimages img.pdf img.extr # not using -j to be extra sure there is no recompression
|
||||
$ compare -metric AE img.jpg img.extr-000.ppm null:
|
||||
1.6301e+06
|
||||
|
||||
If one wants to losslessly convert from any format to PDF with
|
||||
imagemagick, one has to use zip compression:
|
||||
|
||||
$ convert input.jpg -compress Zip output.pdf
|
||||
$ pdfimages img.pdf img.extr # not using -j to be extra sure there is no recompression
|
||||
$ compare -metric AE img.jpg img.extr-000.ppm null:
|
||||
0
|
||||
|
||||
However, this approach will result in PDF files that are a few times larger
|
||||
than the input JPEG or JPEG2000 file.
|
||||
|
||||
img2pdf is able to losslessly embed JPEG and JPEG2000 files into a PDF
|
||||
container without additional overhead (aside from the PDF structure itself),
|
||||
save other graphics formats using lossless zip compression, and produce
|
||||
multi-page PDF files when more than one input image is given.
|
||||
|
||||
Also, since JPEG and JPEG2000 images are not reencoded, conversion with img2pdf
|
||||
is several times faster than with other tools.
|
||||
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
|
||||
-----
|
||||
|
@ -75,52 +61,51 @@ 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
|
||||
$ img2pdf img1.png img2.jpg -o out.pdf
|
||||
|
||||
The detailed documentation can be accessed by running:
|
||||
|
||||
img2pdf --help
|
||||
$ 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 or JPEG2000 file that, when embedded cannot be read
|
||||
by the Adobe Acrobat Reader, please contact me.
|
||||
- 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.
|
||||
|
||||
For lossless conversion of formats other than JPEG or JPEG2000, zip/flate
|
||||
encoding is used. This choice is based on tests I did with a number of images.
|
||||
I converted them into PDF using the lossless variants of the compression
|
||||
formats offered by imagemagick. In all my tests, zip/flate encoding performed
|
||||
best. You can verify my findings using the test_comp.sh script with any input
|
||||
image given as a commandline argument. If you find an input file that is
|
||||
outperformed by another lossless compression method, 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.
|
||||
|
||||
I have not yet figured out how to determine the colorspace of JPEG2000 files.
|
||||
Therefore JPEG2000 files use DeviceRGB by default. For JPEG2000 files with
|
||||
other colorspaces, you must explicitly specify it using the `--colorspace`
|
||||
option.
|
||||
|
||||
It might be possible to store transparency using masks but it is not clear
|
||||
what the utility of such a functionality would be.
|
||||
|
||||
Most vector graphic formats can be losslessly turned into PDF (minus some of
|
||||
the features unsupported by PDF) but img2pdf will currently turn vector
|
||||
graphics into their lossy raster representations. For converting raster
|
||||
graphics to PDF, use another tool like inkscape and then join the resulting
|
||||
pages with a tool like pdftk.
|
||||
|
||||
A configuration file could be used for default options.
|
||||
- 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, dependencies may be installed
|
||||
with the following command:
|
||||
On a Debian- and Ubuntu-based systems, img2pdf can be installed from the
|
||||
official repositories:
|
||||
|
||||
apt-get install python3 python3-pil python3-setuptools
|
||||
$ apt install img2pdf
|
||||
|
||||
You can then install the package using:
|
||||
If you want to install it using pip, you can run:
|
||||
|
||||
$ pip3 install img2pdf
|
||||
|
||||
|
@ -140,6 +125,23 @@ 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
|
||||
|
@ -152,6 +154,10 @@ The package can also be used as a library:
|
|||
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...")
|
||||
|
@ -164,6 +170,45 @@ The package can also be used as a library:
|
|||
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)
|
||||
|
@ -173,3 +218,112 @@ The package can also be used as a library:
|
|||
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.
|
||||
|
|
33
appveyor.yml
Normal file
33
appveyor.yml
Normal file
|
@ -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\*
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.7 KiB |
|
@ -1,2 +0,0 @@
|
|||
[metadata]
|
||||
description-file = README.md
|
73
setup.py
73
setup.py
|
@ -1,62 +1,51 @@
|
|||
import sys
|
||||
from setuptools import setup
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
VERSION = "0.2.4"
|
||||
VERSION = "0.6.1"
|
||||
|
||||
INSTALL_REQUIRES = (
|
||||
'Pillow',
|
||||
"Pillow",
|
||||
"pikepdf",
|
||||
)
|
||||
|
||||
TESTS_REQUIRE = (
|
||||
'pdfrw',
|
||||
)
|
||||
|
||||
if not PY3:
|
||||
INSTALL_REQUIRES += ('enum34',)
|
||||
|
||||
|
||||
setup(
|
||||
name='img2pdf',
|
||||
name="img2pdf",
|
||||
version=VERSION,
|
||||
author="Johannes 'josch' Schauer",
|
||||
author_email='josch@mister-muffin.de',
|
||||
author="Johannes Schauer Marin Rodrigues",
|
||||
author_email="josch@mister-muffin.de",
|
||||
description="Convert images to PDF via direct JPEG inclusion.",
|
||||
long_description=open('README.md').read(),
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
license="LGPL",
|
||||
keywords="jpeg pdf converter",
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Other Audience',
|
||||
'Environment :: Console',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Other Audience",
|
||||
"Environment :: Console",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 '
|
||||
'(LGPLv3)',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent'],
|
||||
url='https://gitlab.mister-muffin.de/josch/img2pdf',
|
||||
download_url='https://gitlab.mister-muffin.de/josch/img2pdf/repository/'
|
||||
'archive.tar.gz?ref=' + VERSION,
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 " "(LGPLv3)",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
url="https://gitlab.mister-muffin.de/josch/img2pdf",
|
||||
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", "jp2"],
|
||||
include_package_data=True,
|
||||
test_suite='tests.test_suite',
|
||||
zip_safe=True,
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
tests_requires=TESTS_REQUIRE,
|
||||
extras_require={
|
||||
'test': TESTS_REQUIRE,
|
||||
"gui": ("tkinter"),
|
||||
},
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
img2pdf = img2pdf:main
|
||||
''',
|
||||
)
|
||||
entry_points={
|
||||
"setuptools.installation": ["eggsecutable = img2pdf:main"],
|
||||
"console_scripts": ["img2pdf = img2pdf:main"],
|
||||
"gui_scripts": ["img2pdf-gui = img2pdf:gui"],
|
||||
},
|
||||
)
|
||||
|
|
4186
src/img2pdf.py
4186
src/img2pdf.py
File diff suppressed because it is too large
Load diff
7116
src/img2pdf_test.py
Executable file
7116
src/img2pdf_test.py
Executable file
File diff suppressed because it is too large
Load diff
92
src/jp2.py
92
src/jp2.py
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2013 Johannes 'josch' Schauer <j.schauer at email.de>
|
||||
# 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
|
||||
|
@ -23,23 +23,22 @@ import struct
|
|||
|
||||
|
||||
def getBox(data, byteStart, noBytes):
|
||||
boxLengthValue = struct.unpack(">I", data[byteStart:byteStart+4])[0]
|
||||
boxType = data[byteStart+4:byteStart+8]
|
||||
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]
|
||||
boxLengthValue = struct.unpack(">Q", data[byteStart + 8 : byteStart + 16])[0]
|
||||
contentsStartOffset = 16
|
||||
if boxLengthValue == 0:
|
||||
boxLengthValue = noBytes-byteStart
|
||||
boxLengthValue = noBytes - byteStart
|
||||
byteEnd = byteStart + boxLengthValue
|
||||
boxContents = data[byteStart+contentsStartOffset:byteEnd]
|
||||
boxContents = data[byteStart + contentsStartOffset : byteEnd]
|
||||
return (boxLengthValue, boxType, byteEnd, boxContents)
|
||||
|
||||
|
||||
def parse_ihdr(data):
|
||||
height = struct.unpack(">I", data[0:4])[0]
|
||||
width = struct.unpack(">I", data[4:8])[0]
|
||||
return width, height
|
||||
height, width, channels, bpp = struct.unpack(">IIHB", data[:11])
|
||||
return width, height, channels, bpp + 1
|
||||
|
||||
|
||||
def parse_colr(data):
|
||||
|
@ -52,14 +51,15 @@ def parse_colr(data):
|
|||
elif enumCS == 17:
|
||||
return "L"
|
||||
else:
|
||||
raise Exception("only sRGB and greyscale color space is supported, "
|
||||
"got %d" % enumCS)
|
||||
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
|
||||
hdpi = ((hnum / hden) * (10**hexp) * 100) / 2.54
|
||||
vdpi = ((vnum / vden) * (10**vexp) * 100) / 2.54
|
||||
return hdpi, vdpi
|
||||
|
||||
|
||||
|
@ -69,9 +69,8 @@ def parse_res(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':
|
||||
boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes)
|
||||
if boxType == b"resc":
|
||||
hdpi, vdpi = parse_resc(boxContents)
|
||||
break
|
||||
return hdpi, vdpi
|
||||
|
@ -83,16 +82,15 @@ def parse_jp2h(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 = parse_ihdr(boxContents)
|
||||
elif boxType == b'colr':
|
||||
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 ':
|
||||
elif boxType == b"res ":
|
||||
hdpi, vdpi = parse_res(boxContents)
|
||||
byteStart = byteEnd
|
||||
return (width, height, colorspace, hdpi, vdpi)
|
||||
return (width, height, colorspace, hdpi, vdpi, channels, bpp)
|
||||
|
||||
|
||||
def parsejp2(data):
|
||||
|
@ -101,10 +99,11 @@ def parsejp2(data):
|
|||
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 = parse_jp2h(boxContents)
|
||||
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:
|
||||
|
@ -114,12 +113,41 @@ def parsejp2(data):
|
|||
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)
|
||||
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 = parsejp2(open(sys.argv[1]).read())
|
||||
sys.stdout.write("width = %d" % width)
|
||||
sys.stdout.write("height = %d" % height)
|
||||
sys.stdout.write("colorspace = %s" % colorspace)
|
||||
|
||||
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)
|
||||
|
|
|
@ -1,639 +0,0 @@
|
|||
import unittest
|
||||
|
||||
import img2pdf
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
from PIL import Image
|
||||
from io import StringIO, BytesIO
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
PdfReaderIO = StringIO
|
||||
else:
|
||||
PdfReaderIO = BytesIO
|
||||
|
||||
|
||||
# convert +set date:create +set date:modify -define png:exclude-chunk=time
|
||||
|
||||
# we define some variables so that the table below can be narrower
|
||||
psl = (972, 504) # --pagesize landscape
|
||||
psp = (504, 972) # --pagesize portrait
|
||||
isl = (756, 324) # --imgsize landscape
|
||||
isp = (324, 756) # --imgsize portrait
|
||||
border = (162, 270) # --border
|
||||
# there is no need to have test cases with the same images with inverted
|
||||
# orientation (landscape/portrait) because --pagesize and --imgsize are
|
||||
# already inverted
|
||||
im1 = (864, 288) # imgpx #1 => 648x216
|
||||
im2 = (1152, 576) # imgpx #2 => 864x432
|
||||
# shortcuts for fit modes
|
||||
f_into = img2pdf.FitMode.into
|
||||
f_fill = img2pdf.FitMode.fill
|
||||
f_exact = img2pdf.FitMode.exact
|
||||
f_shrink = img2pdf.FitMode.shrink
|
||||
f_enlarge = img2pdf.FitMode.enlarge
|
||||
layout_test_cases = [
|
||||
# psp=972x504, psl=504x972, isl=756x324, isp=324x756, border=162:270
|
||||
# --pagesize --border -a pagepdf imgpdf
|
||||
# --imgsize --fit
|
||||
(None, None, None, f_into, 0, (648, 216), (648, 216), # 000
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_into, 1, (648, 216), (648, 216), # 001
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_fill, 0, (648, 216), (648, 216), # 002
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_fill, 1, (648, 216), (648, 216), # 003
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_exact, 0, (648, 216), (648, 216), # 004
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_exact, 1, (648, 216), (648, 216), # 005
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_shrink, 0, (648, 216), (648, 216), # 006
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_shrink, 1, (648, 216), (648, 216), # 007
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_enlarge, 0, (648, 216), (648, 216), # 008
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, None, f_enlarge, 1, (648, 216), (648, 216), # 009
|
||||
(864, 432), (864, 432)),
|
||||
(None, None, border, f_into, 0, (1188, 540), (648, 216), # 010
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_into, 1, (1188, 540), (648, 216), # 011
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_fill, 0, (1188, 540), (648, 216), # 012
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_fill, 1, (1188, 540), (648, 216), # 013
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_exact, 0, (1188, 540), (648, 216), # 014
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_exact, 1, (1188, 540), (648, 216), # 015
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_shrink, 0, (1188, 540), (648, 216), # 016
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_shrink, 1, (1188, 540), (648, 216), # 017
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_enlarge, 0, (1188, 540), (648, 216), # 018
|
||||
(1404, 756), (864, 432)),
|
||||
(None, None, border, f_enlarge, 1, (1188, 540), (648, 216), # 019
|
||||
(1404, 756), (864, 432)),
|
||||
(None, isp, None, f_into, 0, (324, 108), (324, 108), # 020
|
||||
(324, 162), (324, 162)),
|
||||
(None, isp, None, f_into, 1, (324, 108), (324, 108), # 021
|
||||
(324, 162), (324, 162)),
|
||||
(None, isp, None, f_fill, 0, (2268, 756), (2268, 756), # 022
|
||||
(1512, 756), (1512, 756)),
|
||||
(None, isp, None, f_fill, 1, (2268, 756), (2268, 756), # 023
|
||||
(1512, 756), (1512, 756)),
|
||||
(None, isp, None, f_exact, 0, (324, 756), (324, 756), # 024
|
||||
(324, 756), (324, 756)),
|
||||
(None, isp, None, f_exact, 1, (324, 756), (324, 756), # 025
|
||||
(324, 756), (324, 756)),
|
||||
(None, isp, None, f_shrink, 0, (324, 108), (324, 108), # 026
|
||||
(324, 162), (324, 162)),
|
||||
(None, isp, None, f_shrink, 1, (324, 108), (324, 108), # 027
|
||||
(324, 162), (324, 162)),
|
||||
(None, isp, None, f_enlarge, 0, (648, 216), (648, 216), # 028
|
||||
(864, 432), (864, 432)),
|
||||
(None, isp, None, f_enlarge, 1, (648, 216), (648, 216), # 029
|
||||
(864, 432), (864, 432)),
|
||||
(None, isp, border, f_into, 0, (864, 432), (324, 108), # 030
|
||||
(864, 486), (324, 162)),
|
||||
(None, isp, border, f_into, 1, (864, 432), (324, 108), # 031
|
||||
(864, 486), (324, 162)),
|
||||
(None, isp, border, f_fill, 0, (2808, 1080), (2268, 756), # 032
|
||||
(2052, 1080), (1512, 756)),
|
||||
(None, isp, border, f_fill, 1, (2808, 1080), (2268, 756), # 033
|
||||
(2052, 1080), (1512, 756)),
|
||||
(None, isp, border, f_exact, 0, (864, 1080), (324, 756), # 034
|
||||
(864, 1080), (324, 756)),
|
||||
(None, isp, border, f_exact, 1, (864, 1080), (324, 756), # 035
|
||||
(864, 1080), (324, 756)),
|
||||
(None, isp, border, f_shrink, 0, (864, 432), (324, 108), # 036
|
||||
(864, 486), (324, 162)),
|
||||
(None, isp, border, f_shrink, 1, (864, 432), (324, 108), # 037
|
||||
(864, 486), (324, 162)),
|
||||
(None, isp, border, f_enlarge, 0, (1188, 540), (648, 216), # 038
|
||||
(1404, 756), (864, 432)),
|
||||
(None, isp, border, f_enlarge, 1, (1188, 540), (648, 216), # 039
|
||||
(1404, 756), (864, 432)),
|
||||
(None, isl, None, f_into, 0, (756, 252), (756, 252), # 040
|
||||
(648, 324), (648, 324)),
|
||||
(None, isl, None, f_into, 1, (756, 252), (756, 252), # 041
|
||||
(648, 324), (648, 324)),
|
||||
(None, isl, None, f_fill, 0, (972, 324), (972, 324), # 042
|
||||
(756, 378), (756, 378)),
|
||||
(None, isl, None, f_fill, 1, (972, 324), (972, 324), # 043
|
||||
(756, 378), (756, 378)),
|
||||
(None, isl, None, f_exact, 0, (756, 324), (756, 324), # 044
|
||||
(756, 324), (756, 324)),
|
||||
(None, isl, None, f_exact, 1, (756, 324), (756, 324), # 045
|
||||
(756, 324), (756, 324)),
|
||||
(None, isl, None, f_shrink, 0, (648, 216), (648, 216), # 046
|
||||
(648, 324), (648, 324)),
|
||||
(None, isl, None, f_shrink, 1, (648, 216), (648, 216), # 047
|
||||
(648, 324), (648, 324)),
|
||||
(None, isl, None, f_enlarge, 0, (756, 252), (756, 252), # 048
|
||||
(864, 432), (864, 432)),
|
||||
(None, isl, None, f_enlarge, 1, (756, 252), (756, 252), # 049
|
||||
(864, 432), (864, 432)),
|
||||
# psp=972x504, psp=504x972, isl=756x324, isp=324x756, border=162:270
|
||||
# --pagesize --border -a pagepdf imgpdf
|
||||
# --imgsize --fit imgpx
|
||||
(None, isl, border, f_into, 0, (1296, 576), (756, 252), # 050
|
||||
(1188, 648), (648, 324)),
|
||||
(None, isl, border, f_into, 1, (1296, 576), (756, 252), # 051
|
||||
(1188, 648), (648, 324)),
|
||||
(None, isl, border, f_fill, 0, (1512, 648), (972, 324), # 052
|
||||
(1296, 702), (756, 378)),
|
||||
(None, isl, border, f_fill, 1, (1512, 648), (972, 324), # 053
|
||||
(1296, 702), (756, 378)),
|
||||
(None, isl, border, f_exact, 0, (1296, 648), (756, 324), # 054
|
||||
(1296, 648), (756, 324)),
|
||||
(None, isl, border, f_exact, 1, (1296, 648), (756, 324), # 055
|
||||
(1296, 648), (756, 324)),
|
||||
(None, isl, border, f_shrink, 0, (1188, 540), (648, 216), # 056
|
||||
(1188, 648), (648, 324)),
|
||||
(None, isl, border, f_shrink, 1, (1188, 540), (648, 216), # 057
|
||||
(1188, 648), (648, 324)),
|
||||
(None, isl, border, f_enlarge, 0, (1296, 576), (756, 252), # 058
|
||||
(1404, 756), (864, 432)),
|
||||
(None, isl, border, f_enlarge, 1, (1296, 576), (756, 252), # 059
|
||||
(1404, 756), (864, 432)),
|
||||
(psp, None, None, f_into, 0, (504, 972), (504, 168), # 060
|
||||
(504, 972), (504, 252)),
|
||||
(psp, None, None, f_into, 1, (972, 504), (972, 324), # 061
|
||||
(972, 504), (972, 486)),
|
||||
(psp, None, None, f_fill, 0, (504, 972), (2916, 972), # 062
|
||||
(504, 972), (1944, 972)),
|
||||
(psp, None, None, f_fill, 1, (972, 504), (1512, 504), # 063
|
||||
(972, 504), (1008, 504)),
|
||||
(psp, None, None, f_exact, 0, (504, 972), (504, 972), # 064
|
||||
(504, 972), (504, 972)),
|
||||
(psp, None, None, f_exact, 1, (972, 504), (972, 504), # 065
|
||||
(972, 504), (972, 504)),
|
||||
(psp, None, None, f_shrink, 0, (504, 972), (504, 168), # 066
|
||||
(504, 972), (504, 252)),
|
||||
(psp, None, None, f_shrink, 1, (972, 504), (648, 216), # 067
|
||||
(972, 504), (864, 432)),
|
||||
(psp, None, None, f_enlarge, 0, (504, 972), (648, 216), # 068
|
||||
(504, 972), (864, 432)),
|
||||
(psp, None, None, f_enlarge, 1, (972, 504), (972, 324), # 069
|
||||
(972, 504), (972, 486)),
|
||||
(psp, None, border, f_into, 0, None, None, None, None), # 070
|
||||
(psp, None, border, f_into, 1, None, None, None, None), # 071
|
||||
(psp, None, border, f_fill, 0, (504, 972), (1944, 648), # 072
|
||||
(504, 972), (1296, 648)),
|
||||
(psp, None, border, f_fill, 1, (972, 504), (648, 216), # 073
|
||||
(972, 504), (648, 324)),
|
||||
(psp, None, border, f_exact, 0, None, None, None, None), # 074
|
||||
(psp, None, border, f_exact, 1, None, None, None, None), # 075
|
||||
(psp, None, border, f_shrink, 0, None, None, None, None), # 076
|
||||
(psp, None, border, f_shrink, 1, None, None, None, None), # 077
|
||||
(psp, None, border, f_enlarge, 0, (504, 972), (648, 216), # 078
|
||||
(504, 972), (864, 432)),
|
||||
(psp, None, border, f_enlarge, 1, (972, 504), (648, 216), # 079
|
||||
(972, 504), (864, 432)),
|
||||
(psp, isp, None, f_into, 0, (504, 972), (324, 108), # 080
|
||||
(504, 972), (324, 162)),
|
||||
(psp, isp, None, f_into, 1, (972, 504), (324, 108), # 081
|
||||
(972, 504), (324, 162)),
|
||||
(psp, isp, None, f_fill, 0, (504, 972), (2268, 756), # 082
|
||||
(504, 972), (1512, 756)),
|
||||
(psp, isp, None, f_fill, 1, (972, 504), (2268, 756), # 083
|
||||
(972, 504), (1512, 756)),
|
||||
(psp, isp, None, f_exact, 0, (504, 972), (324, 756), # 084
|
||||
(504, 972), (324, 756)),
|
||||
(psp, isp, None, f_exact, 1, (972, 504), (324, 756), # 085
|
||||
(972, 504), (324, 756)),
|
||||
(psp, isp, None, f_shrink, 0, (504, 972), (324, 108), # 086
|
||||
(504, 972), (324, 162)),
|
||||
(psp, isp, None, f_shrink, 1, (972, 504), (324, 108), # 087
|
||||
(972, 504), (324, 162)),
|
||||
(psp, isp, None, f_enlarge, 0, (504, 972), (648, 216), # 088
|
||||
(504, 972), (864, 432)),
|
||||
(psp, isp, None, f_enlarge, 1, (972, 504), (648, 216), # 089
|
||||
(972, 504), (864, 432)),
|
||||
(psp, isp, border, f_into, 0, (504, 972), (324, 108), # 090
|
||||
(504, 972), (324, 162)),
|
||||
(psp, isp, border, f_into, 1, (972, 504), (324, 108), # 091
|
||||
(972, 504), (324, 162)),
|
||||
(psp, isp, border, f_fill, 0, (504, 972), (2268, 756), # 092
|
||||
(504, 972), (1512, 756)),
|
||||
(psp, isp, border, f_fill, 1, (972, 504), (2268, 756), # 093
|
||||
(972, 504), (1512, 756)),
|
||||
(psp, isp, border, f_exact, 0, (504, 972), (324, 756), # 094
|
||||
(504, 972), (324, 756)),
|
||||
(psp, isp, border, f_exact, 1, (972, 504), (324, 756), # 095
|
||||
(972, 504), (324, 756)),
|
||||
(psp, isp, border, f_shrink, 0, (504, 972), (324, 108), # 096
|
||||
(504, 972), (324, 162)),
|
||||
(psp, isp, border, f_shrink, 1, (972, 504), (324, 108), # 097
|
||||
(972, 504), (324, 162)),
|
||||
(psp, isp, border, f_enlarge, 0, (504, 972), (648, 216), # 098
|
||||
(504, 972), (864, 432)),
|
||||
(psp, isp, border, f_enlarge, 1, (972, 504), (648, 216), # 099
|
||||
(972, 504), (864, 432)),
|
||||
# psp=972x504, psp=504x972, isl=756x324, isp=324x756, border=162:270
|
||||
# --pagesize --border -a pagepdf imgpdf
|
||||
# --imgsize --fit imgpx
|
||||
(psp, isl, None, f_into, 0, (504, 972), (756, 252), # 100
|
||||
(504, 972), (648, 324)),
|
||||
(psp, isl, None, f_into, 1, (972, 504), (756, 252), # 101
|
||||
(972, 504), (648, 324)),
|
||||
(psp, isl, None, f_fill, 0, (504, 972), (972, 324), # 102
|
||||
(504, 972), (756, 378)),
|
||||
(psp, isl, None, f_fill, 1, (972, 504), (972, 324), # 103
|
||||
(972, 504), (756, 378)),
|
||||
(psp, isl, None, f_exact, 0, (504, 972), (756, 324), # 104
|
||||
(504, 972), (756, 324)),
|
||||
(psp, isl, None, f_exact, 1, (972, 504), (756, 324), # 105
|
||||
(972, 504), (756, 324)),
|
||||
(psp, isl, None, f_shrink, 0, (504, 972), (648, 216), # 106
|
||||
(504, 972), (648, 324)),
|
||||
(psp, isl, None, f_shrink, 1, (972, 504), (648, 216), # 107
|
||||
(972, 504), (648, 324)),
|
||||
(psp, isl, None, f_enlarge, 0, (504, 972), (756, 252), # 108
|
||||
(504, 972), (864, 432)),
|
||||
(psp, isl, None, f_enlarge, 1, (972, 504), (756, 252), # 109
|
||||
(972, 504), (864, 432)),
|
||||
(psp, isl, border, f_into, 0, (504, 972), (756, 252), # 110
|
||||
(504, 972), (648, 324)),
|
||||
(psp, isl, border, f_into, 1, (972, 504), (756, 252), # 111
|
||||
(972, 504), (648, 324)),
|
||||
(psp, isl, border, f_fill, 0, (504, 972), (972, 324), # 112
|
||||
(504, 972), (756, 378)),
|
||||
(psp, isl, border, f_fill, 1, (972, 504), (972, 324), # 113
|
||||
(972, 504), (756, 378)),
|
||||
(psp, isl, border, f_exact, 0, (504, 972), (756, 324), # 114
|
||||
(504, 972), (756, 324)),
|
||||
(psp, isl, border, f_exact, 1, (972, 504), (756, 324), # 115
|
||||
(972, 504), (756, 324)),
|
||||
(psp, isl, border, f_shrink, 0, (504, 972), (648, 216), # 116
|
||||
(504, 972), (648, 324)),
|
||||
(psp, isl, border, f_shrink, 1, (972, 504), (648, 216), # 117
|
||||
(972, 504), (648, 324)),
|
||||
(psp, isl, border, f_enlarge, 0, (504, 972), (756, 252), # 118
|
||||
(504, 972), (864, 432)),
|
||||
(psp, isl, border, f_enlarge, 1, (972, 504), (756, 252), # 119
|
||||
(972, 504), (864, 432)),
|
||||
(psl, None, None, f_into, 0, (972, 504), (972, 324), # 120
|
||||
(972, 504), (972, 486)),
|
||||
(psl, None, None, f_into, 1, (972, 504), (972, 324), # 121
|
||||
(972, 504), (972, 486)),
|
||||
(psl, None, None, f_fill, 0, (972, 504), (1512, 504), # 122
|
||||
(972, 504), (1008, 504)),
|
||||
(psl, None, None, f_fill, 1, (972, 504), (1512, 504), # 123
|
||||
(972, 504), (1008, 504)),
|
||||
(psl, None, None, f_exact, 0, (972, 504), (972, 504), # 124
|
||||
(972, 504), (972, 504)),
|
||||
(psl, None, None, f_exact, 1, (972, 504), (972, 504), # 125
|
||||
(972, 504), (972, 504)),
|
||||
(psl, None, None, f_shrink, 0, (972, 504), (648, 216), # 126
|
||||
(972, 504), (864, 432)),
|
||||
(psl, None, None, f_shrink, 1, (972, 504), (648, 216), # 127
|
||||
(972, 504), (864, 432)),
|
||||
(psl, None, None, f_enlarge, 0, (972, 504), (972, 324), # 128
|
||||
(972, 504), (972, 486)),
|
||||
(psl, None, None, f_enlarge, 1, (972, 504), (972, 324), # 129
|
||||
(972, 504), (972, 486)),
|
||||
(psl, None, border, f_into, 0, (972, 504), (432, 144), # 130
|
||||
(972, 504), (360, 180)),
|
||||
(psl, None, border, f_into, 1, (972, 504), (432, 144), # 131
|
||||
(972, 504), (360, 180)),
|
||||
(psl, None, border, f_fill, 0, (972, 504), (540, 180), # 132
|
||||
(972, 504), (432, 216)),
|
||||
(psl, None, border, f_fill, 1, (972, 504), (540, 180), # 133
|
||||
(972, 504), (432, 216)),
|
||||
(psl, None, border, f_exact, 0, (972, 504), (432, 180), # 134
|
||||
(972, 504), (432, 180)),
|
||||
(psl, None, border, f_exact, 1, (972, 504), (432, 180), # 135
|
||||
(972, 504), (432, 180)),
|
||||
(psl, None, border, f_shrink, 0, (972, 504), (432, 144), # 136
|
||||
(972, 504), (360, 180)),
|
||||
(psl, None, border, f_shrink, 1, (972, 504), (432, 144), # 137
|
||||
(972, 504), (360, 180)),
|
||||
(psl, None, border, f_enlarge, 0, (972, 504), (648, 216), # 138
|
||||
(972, 504), (864, 432)),
|
||||
(psl, None, border, f_enlarge, 1, (972, 504), (648, 216), # 139
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isp, None, f_into, 0, (972, 504), (324, 108), # 140
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, None, f_into, 1, (972, 504), (324, 108), # 141
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, None, f_fill, 0, (972, 504), (2268, 756), # 142
|
||||
(972, 504), (1512, 756)),
|
||||
(psl, isp, None, f_fill, 1, (972, 504), (2268, 756), # 143
|
||||
(972, 504), (1512, 756)),
|
||||
(psl, isp, None, f_exact, 0, (972, 504), (324, 756), # 144
|
||||
(972, 504), (324, 756)),
|
||||
(psl, isp, None, f_exact, 1, (972, 504), (324, 756), # 145
|
||||
(972, 504), (324, 756)),
|
||||
(psl, isp, None, f_shrink, 0, (972, 504), (324, 108), # 146
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, None, f_shrink, 1, (972, 504), (324, 108), # 147
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, None, f_enlarge, 0, (972, 504), (648, 216), # 148
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isp, None, f_enlarge, 1, (972, 504), (648, 216), # 149
|
||||
(972, 504), (864, 432)),
|
||||
# psp=972x504, psl=504x972, isl=756x324, isp=324x756, border=162:270
|
||||
# --pagesize --border -a pagepdf imgpdf
|
||||
# --imgsize --fit imgpx
|
||||
(psl, isp, border, f_into, 0, (972, 504), (324, 108), # 150
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, border, f_into, 1, (972, 504), (324, 108), # 151
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, border, f_fill, 0, (972, 504), (2268, 756), # 152
|
||||
(972, 504), (1512, 756)),
|
||||
(psl, isp, border, f_fill, 1, (972, 504), (2268, 756), # 153
|
||||
(972, 504), (1512, 756)),
|
||||
(psl, isp, border, f_exact, 0, (972, 504), (324, 756), # 154
|
||||
(972, 504), (324, 756)),
|
||||
(psl, isp, border, f_exact, 1, (972, 504), (324, 756), # 155
|
||||
(972, 504), (324, 756)),
|
||||
(psl, isp, border, f_shrink, 0, (972, 504), (324, 108), # 156
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, border, f_shrink, 1, (972, 504), (324, 108), # 157
|
||||
(972, 504), (324, 162)),
|
||||
(psl, isp, border, f_enlarge, 0, (972, 504), (648, 216), # 158
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isp, border, f_enlarge, 1, (972, 504), (648, 216), # 159
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isl, None, f_into, 0, (972, 504), (756, 252), # 160
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, None, f_into, 1, (972, 504), (756, 252), # 161
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, None, f_fill, 0, (972, 504), (972, 324), # 162
|
||||
(972, 504), (756, 378)),
|
||||
(psl, isl, None, f_fill, 1, (972, 504), (972, 324), # 163
|
||||
(972, 504), (756, 378)),
|
||||
(psl, isl, None, f_exact, 0, (972, 504), (756, 324), # 164
|
||||
(972, 504), (756, 324)),
|
||||
(psl, isl, None, f_exact, 1, (972, 504), (756, 324), # 165
|
||||
(972, 504), (756, 324)),
|
||||
(psl, isl, None, f_shrink, 0, (972, 504), (648, 216), # 166
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, None, f_shrink, 1, (972, 504), (648, 216), # 167
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, None, f_enlarge, 0, (972, 504), (756, 252), # 168
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isl, None, f_enlarge, 1, (972, 504), (756, 252), # 169
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isl, border, f_into, 0, (972, 504), (756, 252), # 170
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, border, f_into, 1, (972, 504), (756, 252), # 171
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, border, f_fill, 0, (972, 504), (972, 324), # 172
|
||||
(972, 504), (756, 378)),
|
||||
(psl, isl, border, f_fill, 1, (972, 504), (972, 324), # 173
|
||||
(972, 504), (756, 378)),
|
||||
(psl, isl, border, f_exact, 0, (972, 504), (756, 324), # 174
|
||||
(972, 504), (756, 324)),
|
||||
(psl, isl, border, f_exact, 1, (972, 504), (756, 324), # 175
|
||||
(972, 504), (756, 324)),
|
||||
(psl, isl, border, f_shrink, 0, (972, 504), (648, 216), # 176
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, border, f_shrink, 1, (972, 504), (648, 216), # 177
|
||||
(972, 504), (648, 324)),
|
||||
(psl, isl, border, f_enlarge, 0, (972, 504), (756, 252), # 178
|
||||
(972, 504), (864, 432)),
|
||||
(psl, isl, border, f_enlarge, 1, (972, 504), (756, 252), # 179
|
||||
(972, 504), (864, 432)),
|
||||
]
|
||||
|
||||
|
||||
def tiff_header_for_ccitt(width, height, img_size, ccitt_group=4):
|
||||
# Quick and dirty TIFF header builder from
|
||||
# https://stackoverflow.com/questions/2641770
|
||||
tiff_header_struct = '<' + '2s' + 'h' + 'l' + 'h' + 'hhll' * 8 + 'h'
|
||||
return struct.pack(
|
||||
tiff_header_struct,
|
||||
b'II', # Byte order indication: Little indian
|
||||
42, # Version number (always 42)
|
||||
8, # Offset to first IFD
|
||||
8, # Number of tags in IFD
|
||||
256, 4, 1, width, # ImageWidth, LONG, 1, width
|
||||
257, 4, 1, height, # ImageLength, LONG, 1, lenght
|
||||
258, 3, 1, 1, # BitsPerSample, SHORT, 1, 1
|
||||
259, 3, 1, ccitt_group, # Compression, SHORT, 1, 4 = CCITT Group 4
|
||||
262, 3, 1, 1, # Threshholding, SHORT, 1, 0 = WhiteIsZero
|
||||
273, 4, 1, struct.calcsize(
|
||||
tiff_header_struct), # StripOffsets, LONG, 1, len of header
|
||||
278, 4, 1, height, # RowsPerStrip, LONG, 1, lenght
|
||||
279, 4, 1, img_size, # StripByteCounts, LONG, 1, size of image
|
||||
0 # last IFD
|
||||
)
|
||||
|
||||
|
||||
def test_suite():
|
||||
class TestImg2Pdf(unittest.TestCase):
|
||||
pass
|
||||
|
||||
for i, (psopt, isopt, border, fit, ao, pspdf1, ispdf1,
|
||||
pspdf2, ispdf2) in enumerate(layout_test_cases):
|
||||
if isopt is not None:
|
||||
isopt = ((img2pdf.ImgSize.abs, isopt[0]),
|
||||
(img2pdf.ImgSize.abs, isopt[1]))
|
||||
|
||||
def layout_handler(
|
||||
self, psopt, isopt, border, fit, ao, pspdf, ispdf, im):
|
||||
layout_fun = img2pdf.get_layout_fun(psopt, isopt, border, fit, ao)
|
||||
try:
|
||||
pwpdf, phpdf, iwpdf, ihpdf = \
|
||||
layout_fun(im[0], im[1], (img2pdf.default_dpi,
|
||||
img2pdf.default_dpi))
|
||||
self.assertEqual((pwpdf, phpdf), pspdf)
|
||||
self.assertEqual((iwpdf, ihpdf), ispdf)
|
||||
except img2pdf.NegativeDimensionError:
|
||||
self.assertEqual(None, pspdf)
|
||||
self.assertEqual(None, ispdf)
|
||||
|
||||
def layout_handler_im1(self, psopt=psopt, isopt=isopt, border=border,
|
||||
fit=fit, ao=ao, pspdf=pspdf1, ispdf=ispdf1):
|
||||
layout_handler(self, psopt, isopt, border, fit, ao, pspdf, ispdf,
|
||||
im1)
|
||||
setattr(TestImg2Pdf, "test_layout_%03d_im1" % i, layout_handler_im1)
|
||||
|
||||
def layout_handler_im2(self, psopt=psopt, isopt=isopt, border=border,
|
||||
fit=fit, ao=ao, pspdf=pspdf2, ispdf=ispdf2):
|
||||
layout_handler(self, psopt, isopt, border, fit, ao, pspdf, ispdf,
|
||||
im2)
|
||||
setattr(TestImg2Pdf, "test_layout_%03d_im2" % i, layout_handler_im2)
|
||||
|
||||
files = os.listdir(os.path.join(HERE, "input"))
|
||||
for with_pdfrw, test_name in [(a, b) for a in [True, False]
|
||||
for b in files]:
|
||||
inputf = os.path.join(HERE, "input", test_name)
|
||||
if not os.path.isfile(inputf):
|
||||
continue
|
||||
outputf = os.path.join(HERE, "output", test_name+".pdf")
|
||||
assert os.path.isfile(outputf)
|
||||
|
||||
def handle(self, f=inputf, out=outputf, with_pdfrw=with_pdfrw):
|
||||
with open(f, "rb") as inf:
|
||||
orig_imgdata = inf.read()
|
||||
output = img2pdf.convert(orig_imgdata, nodate=True,
|
||||
with_pdfrw=with_pdfrw)
|
||||
from pdfrw import PdfReader, PdfName, PdfWriter
|
||||
from pdfrw.py23_diffs import convert_load, convert_store
|
||||
x = PdfReader(PdfReaderIO(convert_load(output)))
|
||||
self.assertEqual(sorted(x.keys()), [PdfName.Info, PdfName.Root,
|
||||
PdfName.Size])
|
||||
self.assertIn(x.Root.Pages.Count, ('1', '2'))
|
||||
if len(x.Root.Pages.Kids) == '1':
|
||||
self.assertEqual(x.Size, '7')
|
||||
self.assertEqual(len(x.Root.Pages.Kids), 1)
|
||||
elif len(x.Root.Pages.Kids) == '2':
|
||||
self.assertEqual(x.Size, '10')
|
||||
self.assertEqual(len(x.Root.Pages.Kids), 2)
|
||||
self.assertEqual(x.Info, {})
|
||||
self.assertEqual(sorted(x.Root.keys()), [PdfName.Pages,
|
||||
PdfName.Type])
|
||||
self.assertEqual(x.Root.Type, PdfName.Catalog)
|
||||
self.assertEqual(sorted(x.Root.Pages.keys()),
|
||||
[PdfName.Count, PdfName.Kids, PdfName.Type])
|
||||
self.assertEqual(x.Root.Pages.Type, PdfName.Pages)
|
||||
orig_img = Image.open(f)
|
||||
for pagenum in range(len(x.Root.Pages.Kids)):
|
||||
# retrieve the original image frame that this page was
|
||||
# generated from
|
||||
orig_img.seek(pagenum)
|
||||
cur_page = x.Root.Pages.Kids[pagenum]
|
||||
|
||||
ndpi = orig_img.info.get("dpi", (96.0, 96.0))
|
||||
# In python3, the returned dpi value for some tiff images will
|
||||
# not be an integer but a float. To make the behaviour of
|
||||
# img2pdf the same between python2 and python3, we convert that
|
||||
# float into an integer by rounding.
|
||||
# Search online for the 72.009 dpi problem for more info.
|
||||
ndpi = (int(round(ndpi[0])), int(round(ndpi[1])))
|
||||
imgwidthpx, imgheightpx = orig_img.size
|
||||
pagewidth = 72.0*imgwidthpx/ndpi[0]
|
||||
pageheight = 72.0*imgheightpx/ndpi[1]
|
||||
|
||||
def format_float(f):
|
||||
if int(f) == f:
|
||||
return str(int(f))
|
||||
else:
|
||||
return ("%.4f" % f).rstrip("0")
|
||||
|
||||
self.assertEqual(sorted(cur_page.keys()),
|
||||
[PdfName.Contents, PdfName.MediaBox,
|
||||
PdfName.Parent, PdfName.Resources,
|
||||
PdfName.Type])
|
||||
self.assertEqual(cur_page.MediaBox,
|
||||
['0', '0', format_float(pagewidth),
|
||||
format_float(pageheight)])
|
||||
self.assertEqual(cur_page.Parent, x.Root.Pages)
|
||||
self.assertEqual(cur_page.Type, PdfName.Page)
|
||||
self.assertEqual(cur_page.Resources.keys(),
|
||||
[PdfName.XObject])
|
||||
self.assertEqual(cur_page.Resources.XObject.keys(),
|
||||
[PdfName.Im0])
|
||||
self.assertEqual(cur_page.Contents.keys(),
|
||||
[PdfName.Length])
|
||||
self.assertEqual(cur_page.Contents.Length,
|
||||
str(len(cur_page.Contents.stream)))
|
||||
self.assertEqual(cur_page.Contents.stream,
|
||||
"q\n%.4f 0 0 %.4f 0.0000 0.0000 cm\n"
|
||||
"/Im0 Do\nQ" % (pagewidth, pageheight))
|
||||
|
||||
imgprops = cur_page.Resources.XObject.Im0
|
||||
|
||||
# test if the filter is valid:
|
||||
self.assertIn(
|
||||
imgprops.Filter, [[PdfName.DCTDecode], [PdfName.JPXDecode],
|
||||
[PdfName.FlateDecode],
|
||||
[PdfName.CCITTFaxDecode]])
|
||||
# test if the colorspace is valid
|
||||
self.assertIn(
|
||||
imgprops.ColorSpace, [PdfName.DeviceGray,
|
||||
PdfName.DeviceRGB,
|
||||
PdfName.DeviceCMYK])
|
||||
|
||||
# test if the image has correct size
|
||||
self.assertEqual(imgprops.Width, str(orig_img.size[0]))
|
||||
self.assertEqual(imgprops.Height, str(orig_img.size[1]))
|
||||
# if the input file is a jpeg then it should've been copied
|
||||
# verbatim into the PDF
|
||||
if imgprops.Filter in [[PdfName.DCTDecode],
|
||||
[PdfName.JPXDecode]]:
|
||||
self.assertEqual(
|
||||
cur_page.Resources.XObject.Im0.stream,
|
||||
convert_load(orig_imgdata))
|
||||
elif imgprops.Filter == [PdfName.CCITTFaxDecode]:
|
||||
tiff_header = tiff_header_for_ccitt(
|
||||
int(imgprops.Width), int(imgprops.Height),
|
||||
int(imgprops.Length), 4)
|
||||
imgio = BytesIO()
|
||||
imgio.write(tiff_header)
|
||||
imgio.write(convert_store(
|
||||
cur_page.Resources.XObject.Im0.stream))
|
||||
imgio.seek(0)
|
||||
im = Image.open(imgio)
|
||||
self.assertEqual(im.tobytes(), orig_img.tobytes())
|
||||
try:
|
||||
im.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
elif imgprops.Filter == [PdfName.FlateDecode]:
|
||||
# otherwise, the data is flate encoded and has to be equal
|
||||
# to the pixel data of the input image
|
||||
imgdata = zlib.decompress(
|
||||
convert_store(cur_page.Resources.XObject.Im0.stream))
|
||||
colorspace = imgprops.ColorSpace
|
||||
if colorspace == PdfName.DeviceGray:
|
||||
colorspace = 'L'
|
||||
elif colorspace == PdfName.DeviceRGB:
|
||||
colorspace = 'RGB'
|
||||
elif colorspace == PdfName.DeviceCMYK:
|
||||
colorspace = 'CMYK'
|
||||
else:
|
||||
raise Exception("invalid colorspace")
|
||||
im = Image.frombytes(colorspace, (int(imgprops.Width),
|
||||
int(imgprops.Height)),
|
||||
imgdata)
|
||||
if orig_img.mode == '1':
|
||||
self.assertEqual(im.tobytes(),
|
||||
orig_img.convert("L").tobytes())
|
||||
elif orig_img.mode not in ("RGB", "L", "CMYK", "CMYK;I"):
|
||||
self.assertEqual(im.tobytes(),
|
||||
orig_img.convert("RGB").tobytes())
|
||||
# the python-pil version 2.3.0-1ubuntu3 in Ubuntu does not
|
||||
# have the close() method
|
||||
try:
|
||||
im.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
# now use pdfrw to parse and then write out both pdfs and check the
|
||||
# result for equality
|
||||
y = PdfReader(out)
|
||||
outx = BytesIO()
|
||||
outy = BytesIO()
|
||||
xwriter = PdfWriter()
|
||||
ywriter = PdfWriter()
|
||||
xwriter.trailer = x
|
||||
ywriter.trailer = y
|
||||
xwriter.write(outx)
|
||||
ywriter.write(outy)
|
||||
self.assertEqual(outx.getvalue(), outy.getvalue())
|
||||
# the python-pil version 2.3.0-1ubuntu3 in Ubuntu does not have the
|
||||
# close() method
|
||||
try:
|
||||
orig_img.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
if with_pdfrw:
|
||||
setattr(TestImg2Pdf, "test_%s_with_pdfrw" % test_name, handle)
|
||||
else:
|
||||
setattr(TestImg2Pdf, "test_%s_without_pdfrw" % test_name, handle)
|
||||
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(TestImg2Pdf),
|
||||
))
|
Binary file not shown.
Before ![]() (image error) Size: 1.9 KiB After ![]() (image error) Size: 1.9 KiB ![]() ![]() |
BIN
src/tests/input/gray.png
Normal file
BIN
src/tests/input/gray.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 814 B |
BIN
src/tests/input/mono.jb2
Normal file
BIN
src/tests/input/mono.jb2
Normal file
Binary file not shown.
BIN
src/tests/input/mono.tif
Normal file
BIN
src/tests/input/mono.tif
Normal file
Binary file not shown.
Binary file not shown.
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 4.9 KiB ![]() ![]() |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/tests/output/gray.png.pdf
Normal file
BIN
src/tests/output/gray.png.pdf
Normal file
Binary file not shown.
BIN
src/tests/output/mono.jb2.pdf
Normal file
BIN
src/tests/output/mono.jb2.pdf
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/tests/output/mono.tif.pdf
Normal file
BIN
src/tests/output/mono.tif.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -16,17 +16,17 @@ for a in `convert -list compress`; do
|
|||
echo "encode:\t$a"
|
||||
convert "$1" -compress $a "`basename $1 .jpg`.pdf"
|
||||
pdfimages "`basename $1 .jpg`.pdf" "`basename $1 .jpg`"
|
||||
/bin/echo -ne "diff:\t"
|
||||
printf "diff:\t"
|
||||
diff=`compare -metric AE "$1" "\`basename $1 .jpg\`-000.ppm" null: 2>&1`
|
||||
if [ "$diff" != "0" ]; then
|
||||
echo "lossy"
|
||||
else
|
||||
echo "lossless"
|
||||
fi
|
||||
/bin/echo -ne "size:\t"
|
||||
printf "size:\t"
|
||||
pdfsize=`stat -c "%s" "\`basename $1 .jpg\`.pdf"`
|
||||
echo "scale=1;$pdfsize/$imsize" | bc
|
||||
/bin/echo -ne "pdf:\t"
|
||||
printf "pdf:\t"
|
||||
grep --max-count=1 --text /Filter "`basename $1 .jpg`.pdf"
|
||||
echo
|
||||
done
|
||||
|
|
17
tox.ini
17
tox.ini
|
@ -1,9 +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 = py27,py35,pypy
|
||||
envlist = py37, py38, py39, py310
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
.[test]
|
||||
pdfrw
|
||||
pytest
|
||||
pikepdf
|
||||
numpy
|
||||
scipy
|
||||
commands =
|
||||
python setup.py test -q
|
||||
|
||||
python -m pytest -vv
|
||||
|
|
Loading…
Reference in a new issue