diff --git a/test.py b/test.py new file mode 100644 index 0000000..a78d439 --- /dev/null +++ b/test.py @@ -0,0 +1,4685 @@ +#!/usr/bin/env python3 + +import sys +import numpy +import scipy.signal +import zlib +import struct +import subprocess +import pytest +import re +import pikepdf +import hashlib +import img2pdf +import os +from io import BytesIO +from PIL import Image +import decimal + + +############################################################################### +# HELPER FUNCTIONS # +############################################################################### + + +def find_closest_palette_color(color, palette): + if color.ndim == 0: + idx = (numpy.abs(palette - color)).argmin() + else: + # naive distance function by computing the euclidean distance in RGB space + idx = ((palette - color) ** 2).sum(axis=-1).argmin() + return palette[idx] + + +def floyd_steinberg(img, palette): + result = numpy.array(img, copy=True) + for y in range(result.shape[0]): + for x in range(result.shape[1]): + oldpixel = result[y, x] + newpixel = find_closest_palette_color(oldpixel, palette) + quant_error = oldpixel - newpixel + result[y, x] = newpixel + if x + 1 < result.shape[1]: + result[y, x + 1] += quant_error * 7 / 16 + if y + 1 < result.shape[0]: + result[y + 1, x - 1] += quant_error * 3 / 16 + result[y + 1, x] += quant_error * 5 / 16 + if x + 1 < result.shape[1] and y + 1 < result.shape[0]: + result[y + 1, x + 1] += quant_error * 1 / 16 + return result + + +def convolve_rgba(img, kernel): + return numpy.stack( + ( + scipy.signal.convolve2d(img[:, :, 0], kernel, "same"), + scipy.signal.convolve2d(img[:, :, 1], kernel, "same"), + scipy.signal.convolve2d(img[:, :, 2], kernel, "same"), + scipy.signal.convolve2d(img[:, :, 3], kernel, "same"), + ), + axis=-1, + ) + + +def rgb2gray(img): + result = numpy.zeros((60, 60), dtype=numpy.dtype("int64")) + count = 0 + for y in range(img.shape[0]): + for x in range(img.shape[1]): + clin = sum(img[y, x] * [0.2126, 0.7152, 0.0722]) / 0xFFFF + if clin <= 0.0031308: + csrgb = 12.92 * clin + else: + csrgb = 1.055 * clin ** (1 / 2.4) - 0.055 + result[y, x] = csrgb * 0xFFFF + count += 1 + # if count == 24: + # raise Exception(result[y, x]) + return result + + +def palettize(img, pal): + result = numpy.zeros((img.shape[0], img.shape[1]), dtype=numpy.dtype("int64")) + for y in range(img.shape[0]): + for x in range(img.shape[1]): + for i, col in enumerate(pal): + if numpy.array_equal(img[y, x], col): + result[y, x] = i + break + else: + raise Exception() + return result + + +# we cannot use zlib.compress() because different compressors may compress the +# same data differently, for example by using different optimizations on +# different architectures: +# https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/R7GD4L5Z6HELCDAL2RDESWR2F3ZXHWVX/ +# +# to make the compressed representation of the uncompressed data bit-by-bit +# identical on all platforms we make use of the compression method 0, that is, +# no compression at all :) +def compress(data): + # two-byte zlib header (rfc1950) + # common header for lowest compression level + # bits 0-3: Compression info, base-2 logarithm of the LZ77 window size, + # minus eight -- 7 indicates a 32K window size + # bits 4-7: Compression method -- 8 is deflate + # bits 8-9: Compression level -- 0 is fastest + # bit 10: preset dictionary -- 0 is none + # bits 11-15: check bits so that the 16-bit unsigned integer stored in MSB + # order is a multiple of 31 + result = b"\x78\x01" + # content is stored in deflate format (rfc1951) + # maximum chunk size is the largest 16 bit unsigned integer + chunksize = 0xFFFF + for i in range(0, len(data), chunksize): + # bits 0-4 are unused + # bits 5-6 indicate compression method -- 0 is no compression + # bit 7 indicates the last chunk + if i * chunksize < len(data) - chunksize: + result += b"\x00" + else: + # last chunck + result += b"\x01" + chunk = data[i : i + chunksize] + # the chunk length as little endian 16 bit unsigned integer + result += struct.pack("I", zlib.adler32(data)) + return result + + +def write_png(data, path, bitdepth, colortype, palette=None): + with open(path, "wb") as f: + f.write(b"\x89PNG\r\n\x1A\n") + # PNG image type Colour type Allowed bit depths + # Greyscale 0 1, 2, 4, 8, 16 + # Truecolour 2 8, 16 + # Indexed-colour 3 1, 2, 4, 8 + # Greyscale with alpha 4 8, 16 + # Truecolour with alpha 6 8, 16 + block = b"IHDR" + struct.pack( + ">IIBBBBB", + data.shape[1], # width + data.shape[0], # height + bitdepth, # bitdepth + colortype, # colortype + 0, # compression + 0, # filtertype + 0, # interlaced + ) + f.write( + struct.pack(">I", len(block) - 4) + + block + + struct.pack(">I", zlib.crc32(block)) + ) + if palette is not None: + block = b"PLTE" + for col in palette: + block += struct.pack(">BBB", col[0], col[1], col[2]) + f.write( + struct.pack(">I", len(block) - 4) + + block + + struct.pack(">I", zlib.crc32(block)) + ) + raw = b"" + for y in range(data.shape[0]): + raw += b"\0" + if bitdepth == 16: + raw += data[y].astype(">u2").tobytes() + elif bitdepth == 8: + raw += data[y].astype(">u1").tobytes() + elif bitdepth in [4, 2, 1]: + valsperbyte = 8 // bitdepth + for x in range(0, data.shape[1], valsperbyte): + val = 0 + for j in range(valsperbyte): + if x + j >= data.shape[1]: + break + val |= (data[y, x + j].astype(">u2") & (2 ** bitdepth - 1)) << ( + (valsperbyte - j - 1) * bitdepth + ) + raw += struct.pack(">B", val) + else: + raise Exception() + compressed = compress(raw) + block = b"IDAT" + compressed + f.write( + struct.pack(">I", len(compressed)) + + block + + struct.pack(">I", zlib.crc32(block)) + ) + block = b"IEND" + f.write(struct.pack(">I", 0) + block + struct.pack(">I", zlib.crc32(block))) + + +def compare_ghostscript(tmpdir, img, pdf, gsdevice="png16m", exact=True): + if gsdevice in ["png16m", "pnggray"]: + ext = "png" + elif gsdevice in ["tiff24nc", "tiff32nc", "tiff48nc"]: + ext = "tiff" + else: + raise Exception("unknown gsdevice: " + gsdevice) + subprocess.check_call( + [ + "gs", + "-dQUIET", + "-dNOPAUSE", + "-dBATCH", + "-sDEVICE=" + gsdevice, + "-r96", + "-sOutputFile=" + str(tmpdir / "gs-") + "%00d." + ext, + str(pdf), + ] + ) + if exact: + subprocess.check_call( + ["compare", "-metric", "AE", str(img), str(tmpdir / "gs-1.") + ext, "null:"] + ) + else: + psnr = subprocess.run( + [ + "compare", + "-metric", + "PSNR", + str(img), + str(tmpdir / "gs-1.") + ext, + "null:", + ], + check=False, + stderr=subprocess.PIPE, + ).stderr + psnr = float(psnr.strip(b"0")) + assert psnr != 0 # or otherwise we would use the exact variant + assert psnr > 50 + (tmpdir / ("gs-1." + ext)).unlink() + + +def compare_poppler(tmpdir, img, pdf, exact=True): + subprocess.check_call( + ["pdftocairo", "-r", "96", "-png", str(pdf), str(tmpdir / "poppler")] + ) + if exact: + subprocess.check_call( + [ + "compare", + "-metric", + "AE", + str(img), + str(tmpdir / "poppler-1.png"), + "null:", + ] + ) + else: + psnr = subprocess.run( + [ + "compare", + "-metric", + "PSNR", + str(img), + str(tmpdir / "poppler-1.png"), + "null:", + ], + check=False, + stderr=subprocess.PIPE, + ).stderr + psnr = float(psnr.strip(b"0")) + assert psnr != 0 # or otherwise we would use the exact variant + assert psnr > 50 + (tmpdir / "poppler-1.png").unlink() + + +def compare_mupdf(tmpdir, img, pdf, exact=True, cmyk=False): + if cmyk: + out = tmpdir / "mupdf.pam" + subprocess.check_call( + ["mutool", "draw", "-r", "96", "-c", "cmyk", "-o", str(out), str(pdf)] + ) + else: + out = tmpdir / "mupdf.png" + subprocess.check_call( + ["mutool", "draw", "-r", "96", "-png", "-o", str(out), str(pdf)] + ) + if exact: + if cmyk: + raise Exception("cmyk cannot be exact") + subprocess.check_call(["compare", "-metric", "AE", str(img), str(out), "null:"]) + else: + psnr = subprocess.run( + ["compare", "-metric", "PSNR", str(img), str(out), "null:"], + check=False, + stderr=subprocess.PIPE, + ).stderr + psnr = float(psnr.strip(b"0")) + assert psnr != 0 # or otherwise we would use the exact variant + assert psnr > 50 + out.unlink() + + +def compare_pdfimages_jpg(tmpdir, img, pdf): + subprocess.check_call(["pdfimages", "-j", str(pdf), str(tmpdir / "images")]) + assert img.read_bytes() == (tmpdir / "images-000.jpg").read_bytes() + (tmpdir / "images-000.jpg").unlink() + + +def compare_pdfimages_jp2(tmpdir, img, pdf): + subprocess.check_call(["pdfimages", "-jp2", str(pdf), str(tmpdir / "images")]) + assert img.read_bytes() == (tmpdir / "images-000.jp2").read_bytes() + (tmpdir / "images-000.jp2").unlink() + + +def compare_pdfimages_tiff(tmpdir, img, pdf): + subprocess.check_call(["pdfimages", "-tiff", str(pdf), str(tmpdir / "images")]) + subprocess.check_call( + ["compare", "-metric", "AE", str(img), str(tmpdir / "images-000.tif"), "null:"] + ) + (tmpdir / "images-000.tif").unlink() + + +def compare_pdfimages_png(tmpdir, img, pdf, exact=True): + subprocess.check_call(["pdfimages", "-png", str(pdf), str(tmpdir / "images")]) + if exact: + subprocess.check_call( + [ + "compare", + "-metric", + "AE", + str(img), + str(tmpdir / "images-000.png"), + "null:", + ] + ) + else: + psnr = subprocess.run( + [ + "compare", + "-metric", + "PSNR", + str(img), + str(tmpdir / "images-000.png"), + "null:", + ], + check=False, + stderr=subprocess.PIPE, + ).stderr + psnr = float(psnr.strip(b"0")) + assert psnr != 0 # or otherwise we would use the exact variant + assert psnr > 50 + (tmpdir / "images-000.png").unlink() + + +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( + # fmt: off + 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 + # fmt: on + ) + + +############################################################################### +# INPUT FIXTURES # +############################################################################### + + +@pytest.fixture(scope="session") +def alpha(): + # gaussian kernel with sigma=3 + kernel = numpy.array( + [ + [0.011362, 0.014962, 0.017649, 0.018648, 0.017649, 0.014962, 0.011362], + [0.014962, 0.019703, 0.02324, 0.024556, 0.02324, 0.019703, 0.014962], + [0.017649, 0.02324, 0.027413, 0.028964, 0.027413, 0.02324, 0.017649], + [0.018648, 0.024556, 0.028964, 0.030603, 0.028964, 0.024556, 0.018648], + [0.017649, 0.02324, 0.027413, 0.028964, 0.027413, 0.02324, 0.017649], + [0.014962, 0.019703, 0.02324, 0.024556, 0.02324, 0.019703, 0.014962], + [0.011362, 0.014962, 0.017649, 0.018648, 0.017649, 0.014962, 0.011362], + ], + numpy.float, + ) + + # constructs a 2D array of a circle with a width of 36 + circle = list() + offsets_36 = [14, 11, 9, 7, 6, 5, 4, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0] + for offs in offsets_36 + offsets_36[::-1]: + circle.append([0] * offs + [1] * (len(offsets_36) - offs) * 2 + [0] * offs) + + alpha = numpy.zeros((60, 60, 4), dtype=numpy.dtype("int64")) + + # draw three circles + for (xpos, ypos, color) in [ + (12, 3, [0xFFFF, 0, 0, 0xFFFF]), + (21, 21, [0, 0xFFFF, 0, 0xFFFF]), + (3, 21, [0, 0, 0xFFFF, 0xFFFF]), + ]: + for x, row in enumerate(circle): + for y, pos in enumerate(row): + if pos: + alpha[y + ypos, x + xpos] += color + alpha = numpy.clip(alpha, 0, 0xFFFF) + alpha = convolve_rgba(alpha, kernel) + return alpha + + +@pytest.fixture(scope="session") +def tmp_alpha_png(tmp_path_factory, alpha): + tmp_alpha_png = tmp_path_factory.mktemp("alpha_png") / "alpha.png" + write_png(alpha, str(tmp_alpha_png), 16, 6) + assert ( + hashlib.md5(tmp_alpha_png.read_bytes()).hexdigest() + == "cc611e80cde3b9b7adb7723801a4e5d4" + ) + yield tmp_alpha_png + tmp_alpha_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_gray1_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + gray16 = rgb2gray(normal16) + tmp_gray1_png = tmp_path_factory.mktemp("gray1_png") / "gray1.png" + write_png( + floyd_steinberg(gray16, numpy.arange(2) / 0x1 * 0xFFFF) / 0xFFFF * 0x1, + str(tmp_gray1_png), + 1, + 0, + ) + assert ( + hashlib.md5(tmp_gray1_png.read_bytes()).hexdigest() + == "ff4d9f18de39be879926be2e65990167" + ) + yield tmp_gray1_png + tmp_gray1_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_gray2_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + gray16 = rgb2gray(normal16) + tmp_gray2_png = tmp_path_factory.mktemp("gray2_png") / "gray2.png" + write_png( + floyd_steinberg(gray16, numpy.arange(4) / 0x3 * 0xFFFF) / 0xFFFF * 0x3, + str(tmp_gray2_png), + 2, + 0, + ) + assert ( + hashlib.md5(tmp_gray2_png.read_bytes()).hexdigest() + == "d51900476658a1c9dd26a7b27db8a21f" + ) + yield tmp_gray2_png + tmp_gray2_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_gray4_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + gray16 = rgb2gray(normal16) + tmp_gray4_png = tmp_path_factory.mktemp("gray4_png") / "gray4.png" + write_png( + floyd_steinberg(gray16, numpy.arange(16) / 0xF * 0xFFFF) / 0xFFFF * 0xF, + str(tmp_gray4_png), + 4, + 0, + ) + assert ( + hashlib.md5(tmp_gray4_png.read_bytes()).hexdigest() + == "722223ba74be9cba1af4a549076b70d3" + ) + yield tmp_gray4_png + tmp_gray4_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_gray8_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + gray16 = rgb2gray(normal16) + tmp_gray8_png = tmp_path_factory.mktemp("gray8_png") / "gray8.png" + write_png(gray16 / 0xFFFF * 0xFF, tmp_gray8_png, 8, 0) + assert ( + hashlib.md5(tmp_gray8_png.read_bytes()).hexdigest() + == "2320216faa5a10bf0f5f04ebce07f8e1" + ) + yield tmp_gray8_png + tmp_gray8_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_gray16_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + gray16 = rgb2gray(normal16) + tmp_gray16_png = tmp_path_factory.mktemp("gray16_png") / "gray16.png" + write_png(gray16, str(tmp_gray16_png), 16, 0) + assert ( + hashlib.md5(tmp_gray16_png.read_bytes()).hexdigest() + == "706175887af8ca1a33cfd93449f970df" + ) + yield tmp_gray16_png + tmp_gray16_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_inverse_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_inverse_png = tmp_path_factory.mktemp("inverse_png") / "inverse.png" + write_png(0xFF - normal16 / 0xFFFF * 0xFF, str(tmp_inverse_png), 8, 2) + assert ( + hashlib.md5(tmp_inverse_png.read_bytes()).hexdigest() + == "35a47d6ae6de8c9d0b31aa0cda8648f3" + ) + yield tmp_inverse_png + tmp_inverse_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_normal16_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_normal16_png = tmp_path_factory.mktemp("normal16_png") / "normal16.png" + write_png(normal16, str(tmp_normal16_png), 16, 2) + assert ( + hashlib.md5(tmp_normal16_png.read_bytes()).hexdigest() + == "6ad810399058a87d8145d8d9a7734da5" + ) + yield tmp_normal16_png + tmp_normal16_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_normal_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_normal_png = tmp_path_factory.mktemp("normal_png") / "normal.png" + write_png(normal16 / 0xFFFF * 0xFF, str(tmp_normal_png), 8, 2) + assert ( + hashlib.md5(tmp_normal_png.read_bytes()).hexdigest() + == "c8d2e1f116f31ecdeae050524efca7b6" + ) + yield tmp_normal_png + tmp_normal_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_palette1_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_palette1_png = tmp_path_factory.mktemp("palette1_png") / "palette1.png" + # don't choose black and white or otherwise imagemagick will classify the + # image as bilevel with 8/1-bit depth instead of palette with 8-bit color + # don't choose gray colors or otherwise imagemagick will classify the + # image as grayscale + pal1 = numpy.array( + [[0x01, 0x02, 0x03], [0xFE, 0xFD, 0xFC]], dtype=numpy.dtype("int64") + ) + write_png( + palettize( + floyd_steinberg(normal16, pal1 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal1 + ), + str(tmp_palette1_png), + 1, + 3, + pal1, + ) + assert ( + hashlib.md5(tmp_palette1_png.read_bytes()).hexdigest() + == "18a3dfca369f976996ef93389ddfad61" + ) + yield tmp_palette1_png + tmp_palette1_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_palette2_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_palette2_png = tmp_path_factory.mktemp("palette2_png") / "palette2.png" + # choose values slightly off red, lime and blue because otherwise + # imagemagick will classify the image as Depth: 8/1-bit + pal2 = numpy.array( + [[0, 0, 0], [0xFE, 0, 0], [0, 0xFE, 0], [0, 0, 0xFE]], + dtype=numpy.dtype("int64"), + ) + write_png( + palettize( + floyd_steinberg(normal16, pal2 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal2 + ), + str(tmp_palette2_png), + 2, + 3, + pal2, + ) + assert ( + hashlib.md5(tmp_palette2_png.read_bytes()).hexdigest() + == "d38646afa6fa0714be9badef25ff9392" + ) + yield tmp_palette2_png + tmp_palette2_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_palette4_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_palette4_png = tmp_path_factory.mktemp("palette4_png") / "palette4.png" + # windows 16 color palette + pal4 = numpy.array( + [ + [0x00, 0x00, 0x00], + [0x80, 0x00, 0x00], + [0x00, 0x80, 0x00], + [0x80, 0x80, 0x00], + [0x00, 0x00, 0x80], + [0x80, 0x00, 0x80], + [0x00, 0x80, 0x80], + [0xC0, 0xC0, 0xC0], + [0x80, 0x80, 0x80], + [0xFF, 0x00, 0x00], + [0x00, 0xFF, 0x00], + [0xFF, 0x00, 0x00], + [0x00, 0xFF, 0x00], + [0xFF, 0x00, 0xFF], + [0x00, 0xFF, 0x00], + [0xFF, 0xFF, 0xFF], + ], + dtype=numpy.dtype("int64"), + ) + write_png( + palettize( + floyd_steinberg(normal16, pal4 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal4 + ), + str(tmp_palette4_png), + 4, + 3, + pal4, + ) + assert ( + hashlib.md5(tmp_palette4_png.read_bytes()).hexdigest() + == "e1c59e68a68fca3273b6dc164d526ed7" + ) + yield tmp_palette4_png + tmp_palette4_png.unlink() + + +@pytest.fixture(scope="session") +def tmp_palette8_png(tmp_path_factory, alpha): + normal16 = alpha[:, :, 0:3] + tmp_palette8_png = tmp_path_factory.mktemp("palette8_png") / "palette8.png" + # create a 256 color palette by first writing 16 shades of gray + # and then writing an array of RGB colors with 6, 8 and 5 levels + # for red, green and blue, respectively + pal8 = numpy.zeros((256, 3), dtype=numpy.dtype("int64")) + i = 0 + for gray in range(15, 255, 15): + pal8[i] = [gray, gray, gray] + i += 1 + for red in 0, 0x33, 0x66, 0x99, 0xCC, 0xFF: + for green in 0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF: + for blue in 0, 0x40, 0x80, 0xBF, 0xFF: + pal8[i] = [red, green, blue] + i += 1 + assert i == 256 + write_png( + palettize( + floyd_steinberg(normal16, pal8 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal8 + ), + str(tmp_palette8_png), + 8, + 3, + pal8, + ) + assert ( + hashlib.md5(tmp_palette8_png.read_bytes()).hexdigest() + == "50bf09eb3571901f0bf642b9a733038c" + ) + yield tmp_palette8_png + tmp_palette8_png.unlink() + + +@pytest.fixture(scope="session") +def jpg_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("jpg") / "in.jpg" + subprocess.check_call(["convert", str(tmp_normal_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: JPEG \(Joint Photographic Experts Group JFIF format\)$", + r"^ Mime type: image/jpeg$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: JPEG$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def jpg_rot_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("jpg_rot") / "in.jpg" + subprocess.check_call(["convert", str(tmp_normal_png), str(in_img)]) + subprocess.check_call( + ["exiftool", "-overwrite_original", "-all=", str(in_img), "-n"] + ) + subprocess.check_call( + [ + "exiftool", + "-overwrite_original", + "-Orientation=6", + "-XResolution=96", + "-YResolution=96", + "-n", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: JPEG \(Joint Photographic Experts Group JFIF format\)$", + r"^ Mime type: image/jpeg$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Resolution: 96x96$", + r"^ Units: PixelsPerInch$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: JPEG$", + r"^ Orientation: RightTop$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def jpg_cmyk_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("jpg_cmyk") / "in.jpg" + subprocess.check_call( + ["convert", str(tmp_normal_png), "-colorspace", "cmyk", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: JPEG \(Joint Photographic Experts Group JFIF format\)$", + r"^ Mime type: image/jpeg$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: CMYK$", + r"^ Type: ColorSeparation$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: JPEG$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def jpg_2000_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("jpg_2000") / "in.jp2" + subprocess.check_call(["convert", str(tmp_normal_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: JP2 \(JPEG-2000 File Format Syntax\)$", + r"^ Mime type: image/jp2$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: JPEG2000$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def png_rgb8_img(tmp_normal_png): + in_img = tmp_normal_png + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 2$", + r"^ png:IHDR.color_type: 2 \(Truecolor\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return in_img + + +@pytest.fixture(scope="session") +def png_rgb16_img(tmp_normal16_png): + in_img = tmp_normal16_png + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 16$", + r"^ png:IHDR.bit_depth: 16$", + r"^ png:IHDR.color-type-orig: 2$", + r"^ png:IHDR.color_type: 2 \(Truecolor\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return in_img + + +@pytest.fixture(scope="session") +def png_rgba8_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("png_rgba8") / "in.png" + subprocess.check_call( + ["convert", str(tmp_alpha_png), "-depth", "8", "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColorAlpha$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 6$", + r"^ png:IHDR.color_type: 6 \(RGBA\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def png_rgba16_img(tmp_alpha_png): + in_img = tmp_alpha_png + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColorAlpha$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 16$", + r"^ png:IHDR.bit_depth: 16$", + r"^ png:IHDR.color-type-orig: 6$", + r"^ png:IHDR.color_type: 6 \(RGBA\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return in_img + + +@pytest.fixture(scope="session") +def png_gray8a_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("png_gray8a") / "in.png" + subprocess.check_call( + [ + "convert", + str(tmp_alpha_png), + "-colorspace", + "Gray", + "-dither", + "FloydSteinberg", + "-colors", + "256", + "-depth", + "8", + "-strip", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: GrayscaleAlpha$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 4$", + r"^ png:IHDR.color_type: 4 \(GrayAlpha\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def png_gray16a_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("png_gray16a") / "in.png" + subprocess.check_call( + [ + "convert", + str(tmp_alpha_png), + "-colorspace", + "Gray", + "-depth", + "16", + "-strip", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: GrayscaleAlpha$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 16$", + r"^ png:IHDR.bit_depth: 16$", + r"^ png:IHDR.color-type-orig: 4$", + r"^ png:IHDR.color_type: 4 \(GrayAlpha\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def png_interlaced_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("png_interlaced") / "in.png" + subprocess.check_call( + ["convert", str(tmp_normal_png), "-interlace", "PNG", "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 2$", + r"^ png:IHDR.color_type: 2 \(Truecolor\)$", + r"^ png:IHDR.interlace_method: 1 \(Adam7 method\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def png_gray1_img(tmp_path_factory, tmp_gray1_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_gray1_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Depth: 8/1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 1$", + r"^ png:IHDR.bit_depth: 1$", + r"^ png:IHDR.color-type-orig: 0$", + r"^ png:IHDR.color_type: 0 \(Grayscale\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_gray1_png + + +@pytest.fixture(scope="session") +def png_gray2_img(tmp_path_factory, tmp_gray2_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_gray2_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Depth: 8/2-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 2$", + r"^ png:IHDR.bit_depth: 2$", + r"^ png:IHDR.color-type-orig: 0$", + r"^ png:IHDR.color_type: 0 \(Grayscale\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_gray2_png + + +@pytest.fixture(scope="session") +def png_gray4_img(tmp_path_factory, tmp_gray4_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_gray4_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Depth: 8/4-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 4$", + r"^ png:IHDR.bit_depth: 4$", + r"^ png:IHDR.color-type-orig: 0$", + r"^ png:IHDR.color_type: 0 \(Grayscale\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_gray4_png + + +@pytest.fixture(scope="session") +def png_gray8_img(tmp_path_factory, tmp_gray8_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_gray8_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 0$", + r"^ png:IHDR.color_type: 0 \(Grayscale\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_gray8_png + + +@pytest.fixture(scope="session") +def png_gray16_img(tmp_path_factory, tmp_gray16_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_gray16_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 16$", + r"^ png:IHDR.bit_depth: 16$", + r"^ png:IHDR.color-type-orig: 0$", + r"^ png:IHDR.color_type: 0 \(Grayscale\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_gray16_png + + +@pytest.fixture(scope="session") +def png_palette1_img(tmp_path_factory, tmp_palette1_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_palette1_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 1$", + r"^ png:IHDR.bit_depth: 1$", + r"^ png:IHDR.color-type-orig: 3$", + r"^ png:IHDR.color_type: 3 \(Indexed\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_palette1_png + + +@pytest.fixture(scope="session") +def png_palette2_img(tmp_path_factory, tmp_palette2_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_palette2_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 2$", + r"^ png:IHDR.bit_depth: 2$", + r"^ png:IHDR.color-type-orig: 3$", + r"^ png:IHDR.color_type: 3 \(Indexed\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_palette2_png + + +@pytest.fixture(scope="session") +def png_palette4_img(tmp_path_factory, tmp_palette4_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_palette4_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 4$", + r"^ png:IHDR.bit_depth: 4$", + r"^ png:IHDR.color-type-orig: 3$", + r"^ png:IHDR.color_type: 3 \(Indexed\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_palette4_png + + +@pytest.fixture(scope="session") +def png_palette8_img(tmp_path_factory, tmp_palette8_png): + identify = subprocess.check_output(["identify", "-verbose", tmp_palette8_png]) + expected = [ + r"^ Format: PNG \(Portable Network Graphics\)$", + r"^ Mime type: image/png$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ png:IHDR.bit-depth-orig: 8$", + r"^ png:IHDR.bit_depth: 8$", + r"^ png:IHDR.color-type-orig: 3$", + r"^ png:IHDR.color_type: 3 \(Indexed\)$", + r"^ png:IHDR.interlace_method: 0 \(Not interlaced\)$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + return tmp_palette8_png + + +@pytest.fixture(scope="session") +def gif_transparent_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("gif_transparent_img") / "in.gif" + subprocess.check_call(["convert", str(tmp_alpha_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: PaletteAlpha$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 256$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def gif_palette1_img(tmp_path_factory, tmp_palette1_png): + in_img = tmp_path_factory.mktemp("gif_palette1_img") / "in.gif" + subprocess.check_call(["convert", str(tmp_palette1_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 2$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def gif_palette2_img(tmp_path_factory, tmp_palette2_png): + in_img = tmp_path_factory.mktemp("gif_palette2_img") / "in.gif" + subprocess.check_call(["convert", str(tmp_palette2_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 4$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def gif_palette4_img(tmp_path_factory, tmp_palette4_png): + in_img = tmp_path_factory.mktemp("gif_palette4_img") / "in.gif" + subprocess.check_call(["convert", str(tmp_palette4_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 16$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def gif_palette8_img(tmp_path_factory, tmp_palette8_png): + in_img = tmp_path_factory.mktemp("gif_palette8_img") / "in.gif" + subprocess.check_call(["convert", str(tmp_palette8_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 256$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def gif_animation_img(tmp_path_factory, tmp_normal_png, tmp_inverse_png): + in_img = tmp_path_factory.mktemp("gif_animation_img") / "in.gif" + subprocess.check_call( + ["convert", tmp_normal_png, tmp_inverse_png, "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", str(in_img) + "[0]"]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 256$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + identify = subprocess.check_output(["identify", "-verbose", str(in_img) + "[1]"]) + expected = [ + r"^ Format: GIF \(CompuServe graphics interchange format\)$", + r"^ Mime type: image/gif$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 256$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: LZW$", + r"^ Scene: 1$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_float_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("tiff_float_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_normal_png), + "-depth", + "32", + "-define", + "quantum:format=floating-point", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 32/(8|16)-bit$", # imagemagick may produce a Depth: 32/8-bit or 32/16-bit image + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ quantum:format: floating-point$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_cmyk8_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("tiff_cmyk8") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_normal_png), "-colorspace", "cmyk", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: CMYK$", + r"^ Type: ColorSeparation$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: separated$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_cmyk16_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("tiff_cmyk16") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_normal_png), + "-depth", + "16", + "-colorspace", + "cmyk", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: CMYK$", + r"^ Type: ColorSeparation$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: separated$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgb8_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("tiff_rgb8") / "in.tiff" + subprocess.check_call(["convert", str(tmp_normal_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgb12_img(tmp_path_factory, tmp_normal16_png): + in_img = tmp_path_factory.mktemp("tiff_rgb8") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_normal16_png), "-depth", "12", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 12(/16)?-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgb14_img(tmp_path_factory, tmp_normal16_png): + in_img = tmp_path_factory.mktemp("tiff_rgb8") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_normal16_png), "-depth", "14", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 14(/16)?-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgb16_img(tmp_path_factory, tmp_normal16_png): + in_img = tmp_path_factory.mktemp("tiff_rgb8") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_normal16_png), "-depth", "16", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgba8_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("tiff_rgba8") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_alpha_png), "-depth", "8", "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColorAlpha$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unassociated$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_rgba16_img(tmp_path_factory, tmp_alpha_png): + in_img = tmp_path_factory.mktemp("tiff_rgba16") / "in.tiff" + subprocess.check_call( + ["convert", str(tmp_alpha_png), "-depth", "16", "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColorAlpha$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unassociated$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_gray1_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_gray1") / "in.tiff" + subprocess.check_call(["convert", str(tmp_gray1_png), "-depth", "1", str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_gray2_img(tmp_path_factory, tmp_gray2_png): + in_img = tmp_path_factory.mktemp("tiff_gray2") / "in.tiff" + subprocess.check_call(["convert", str(tmp_gray2_png), "-depth", "2", str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 2-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_gray4_img(tmp_path_factory, tmp_gray4_png): + in_img = tmp_path_factory.mktemp("tiff_gray4") / "in.tiff" + subprocess.check_call(["convert", str(tmp_gray4_png), "-depth", "4", str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 4-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_gray8_img(tmp_path_factory, tmp_gray8_png): + in_img = tmp_path_factory.mktemp("tiff_gray8") / "in.tiff" + subprocess.check_call(["convert", str(tmp_gray8_png), "-depth", "8", str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_gray16_img(tmp_path_factory, tmp_gray16_png): + in_img = tmp_path_factory.mktemp("tiff_gray16") / "in.tiff" + subprocess.check_call(["convert", str(tmp_gray16_png), "-depth", "16", str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Grayscale$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 16-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_multipage_img(tmp_path_factory, tmp_normal_png, tmp_inverse_png): + in_img = tmp_path_factory.mktemp("tiff_multipage_img") / "in.tiff" + subprocess.check_call( + ["convert", tmp_normal_png, tmp_inverse_png, "-strip", str(in_img)] + ) + identify = subprocess.check_output(["identify", "-verbose", str(in_img) + "[0]"]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + identify = subprocess.check_output(["identify", "-verbose", str(in_img) + "[1]"]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: TrueColor$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: RGB$", + r"^ Scene: 1$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_palette1_img(tmp_path_factory, tmp_palette1_png): + in_img = tmp_path_factory.mktemp("tiff_palette1_img") / "in.tiff" + subprocess.check_call(["convert", str(tmp_palette1_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1/8-bit$", + r"^ Colormap entries: 2$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: palette$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_palette2_img(tmp_path_factory, tmp_palette2_png): + in_img = tmp_path_factory.mktemp("tiff_palette2_img") / "in.tiff" + subprocess.check_call(["convert", str(tmp_palette2_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 2/8-bit$", + r"^ Colormap entries: 4$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: palette$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_palette4_img(tmp_path_factory, tmp_palette4_png): + in_img = tmp_path_factory.mktemp("tiff_palette4_img") / "in.tiff" + subprocess.check_call(["convert", str(tmp_palette4_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 4/8-bit$", + r"^ Colormap entries: 16$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: palette$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_palette8_img(tmp_path_factory, tmp_palette8_png): + in_img = tmp_path_factory.mktemp("tiff_palette8_img") / "in.tiff" + subprocess.check_call(["convert", str(tmp_palette8_png), str(in_img)]) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: sRGB$", + r"^ Type: Palette$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 8-bit$", + r"^ Colormap entries: 256$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Zip$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: palette$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_lsb_m2l_white_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_white_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=lsb", + "-define", + "tiff:fill-order=msb", + "-define", + "quantum:polarity=min-is-white", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-white$", + r"^ tiff:rows-per-strip: 60$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Bits/Sample: 1", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-white", + r"^ FillOrder: msb-to-lsb", + r"^ Samples/Pixel: 1", + r"^ Rows/Strip: 60", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_msb_m2l_white_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_msb_m2l_white_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=msb", + "-define", + "tiff:fill-order=msb", + "-define", + "quantum:polarity=min-is-white", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: MSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: msb$", + r"^ tiff:photometric: min-is-white$", + r"^ tiff:rows-per-strip: 60$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Bits/Sample: 1", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-white", + r"^ FillOrder: msb-to-lsb", + r"^ Samples/Pixel: 1", + r"^ Rows/Strip: 60", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_msb_l2m_white_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_msb_l2m_white_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=msb", + "-define", + "tiff:fill-order=lsb", + "-define", + "quantum:polarity=min-is-white", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: MSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: msb$", + r"^ tiff:photometric: min-is-white$", + r"^ tiff:rows-per-strip: 60$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Bits/Sample: 1", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-white", + r"^ FillOrder: lsb-to-msb", + r"^ Samples/Pixel: 1", + r"^ Rows/Strip: 60", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_lsb_m2l_black_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_black_img") / "in.tiff" + # "-define quantum:polarity=min-is-black" requires ImageMagick with: + # https://github.com/ImageMagick/ImageMagick/commit/00730551f0a34328685c59d0dde87dd9e366103a + # or at least 7.0.8-11 from Aug 29, 2018 + # also see: https://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=34605 + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=lsb", + "-define", + "tiff:fill-order=msb", + "-define", + "quantum:polarity=min-is-black", + str(in_img), + ] + ) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-black$", + r"^ tiff:rows-per-strip: 60$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Bits/Sample: 1", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-black", + r"^ FillOrder: msb-to-lsb", + r"^ Samples/Pixel: 1", + r"^ Rows/Strip: 60", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_nometa1_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_nometa1_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=lsb", + "-define", + "tiff:fill-order=msb", + "-define", + "quantum:polarity=min-is-white", + str(in_img), + ] + ) + subprocess.check_call( + ["tiffset", "-u", "258", str(in_img)] + ) # remove BitsPerSample (258) + subprocess.check_call( + ["tiffset", "-u", "266", str(in_img)] + ) # remove FillOrder (266) + subprocess.check_call( + ["tiffset", "-u", "277", str(in_img)] + ) # remove SamplesPerPixel (277) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-white$", + r"^ tiff:rows-per-strip: 60$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-white", + r"^ Rows/Strip: 60", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + unexpected = [" Bits/Sample: ", " FillOrder: ", " Samples/Pixel: "] + for e in unexpected: + assert e not in tiffinfo.decode("utf8") + yield in_img + in_img.unlink() + + +@pytest.fixture(scope="session") +def tiff_ccitt_nometa2_img(tmp_path_factory, tmp_gray1_png): + in_img = tmp_path_factory.mktemp("tiff_ccitt_nometa2_img") / "in.tiff" + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-compress", + "group4", + "-define", + "tiff:endian=lsb", + "-define", + "tiff:fill-order=msb", + "-define", + "quantum:polarity=min-is-white", + str(in_img), + ] + ) + subprocess.check_call( + ["tiffset", "-u", "278", str(in_img)] + ) # remove RowsPerStrip (278) + identify = subprocess.check_output(["identify", "-verbose", in_img]) + expected = [ + r"^ Format: TIFF \(Tagged Image File Format\)$", + r"^ Mime type: image/tiff$", + r"^ Geometry: 60x60\+0\+0$", + r"^ Colorspace: Gray$", + r"^ Type: Bilevel$", + r"^ Endiann?ess: LSB$", + r"^ Depth: 1-bit$", + r"^ Page geometry: 60x60\+0\+0$", + r"^ Compression: Group4$", + r"^ tiff:alpha: unspecified$", + r"^ tiff:endian: lsb$", + r"^ tiff:photometric: min-is-white$", + ] + for e in expected: + assert re.search(e, identify.decode("utf8"), re.MULTILINE) + unexpected = [" tiff:rows-per-strip: "] + for e in unexpected: + assert e not in identify.decode("utf8") + tiffinfo = subprocess.check_output(["tiffinfo", in_img]) + expected = [ + r"^ Image Width: 60 Image Length: 60", + r"^ Bits/Sample: 1", + r"^ Compression Scheme: CCITT Group 4", + r"^ Photometric Interpretation: min-is-white", + r"^ FillOrder: msb-to-lsb", + r"^ Samples/Pixel: 1", + ] + for e in expected: + assert re.search(e, tiffinfo.decode("utf8"), re.MULTILINE) + unexpected = [" Rows/Strip: "] + for e in unexpected: + assert e not in tiffinfo.decode("utf8") + yield in_img + in_img.unlink() + + +############################################################################### +# OUTPUT FIXTURES # +############################################################################### + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def jpg_pdf(tmp_path_factory, jpg_img, request): + out_pdf = tmp_path_factory.mktemp("jpg_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + jpg_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.Filter == "/DCTDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def jpg_rot_pdf(tmp_path_factory, jpg_rot_img, request): + out_pdf = tmp_path_factory.mktemp("jpg_rot_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + jpg_rot_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.Filter == "/DCTDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + assert p.pages[0].Rotate == 90 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def jpg_cmyk_pdf(tmp_path_factory, jpg_cmyk_img, request): + out_pdf = tmp_path_factory.mktemp("jpg_cmyk_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + jpg_cmyk_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceCMYK" + assert p.pages[0].Resources.XObject.Im0.Decode == pikepdf.Array( + [1, 0, 1, 0, 1, 0, 1, 0] + ) + assert p.pages[0].Resources.XObject.Im0.Filter == "/DCTDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def jpg_2000_pdf(tmp_path_factory, jpg_2000_img, request): + out_pdf = tmp_path_factory.mktemp("jpg_2000_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + jpg_2000_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.Filter == "/JPXDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_rgb8_pdf(tmp_path_factory, png_rgb8_img, request): + out_pdf = tmp_path_factory.mktemp("png_rgb8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + png_rgb8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_rgb16_pdf(tmp_path_factory, png_rgb16_img, request): + out_pdf = tmp_path_factory.mktemp("png_rgb16_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + png_rgb16_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 16 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 16 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_interlaced_pdf(tmp_path_factory, png_interlaced_img, request): + out_pdf = tmp_path_factory.mktemp("png_interlaced_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + png_interlaced_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray1_pdf(tmp_path_factory, tmp_gray1_png, request): + out_pdf = tmp_path_factory.mktemp("png_gray1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_gray1_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray2_pdf(tmp_path_factory, tmp_gray2_png, request): + out_pdf = tmp_path_factory.mktemp("png_gray2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_gray2_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray4_pdf(tmp_path_factory, tmp_gray4_png, request): + out_pdf = tmp_path_factory.mktemp("png_gray4_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_gray4_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray8_pdf(tmp_path_factory, tmp_gray8_png, request): + out_pdf = tmp_path_factory.mktemp("png_gray8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_gray8_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray16_pdf(tmp_path_factory, tmp_gray16_png, request): + out_pdf = tmp_path_factory.mktemp("png_gray16_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_gray16_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 16 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 16 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def png_palette1_pdf(tmp_path_factory, tmp_palette1_png, request): + out_pdf = tmp_path_factory.mktemp("png_palette1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_palette1_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def png_palette2_pdf(tmp_path_factory, tmp_palette2_png, request): + out_pdf = tmp_path_factory.mktemp("png_palette2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_palette2_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def png_palette4_pdf(tmp_path_factory, tmp_palette4_png, request): + out_pdf = tmp_path_factory.mktemp("png_palette4_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_palette4_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def png_palette8_pdf(tmp_path_factory, tmp_palette8_png, request): + out_pdf = tmp_path_factory.mktemp("png_palette8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tmp_palette8_png, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def gif_palette1_pdf(tmp_path_factory, gif_palette1_img, request): + out_pdf = tmp_path_factory.mktemp("gif_palette1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + gif_palette1_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def gif_palette2_pdf(tmp_path_factory, gif_palette2_img, request): + out_pdf = tmp_path_factory.mktemp("gif_palette2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + gif_palette2_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def gif_palette4_pdf(tmp_path_factory, gif_palette4_img, request): + out_pdf = tmp_path_factory.mktemp("gif_palette4_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + gif_palette4_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def gif_palette8_pdf(tmp_path_factory, gif_palette8_img, request): + out_pdf = tmp_path_factory.mktemp("gif_palette8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + gif_palette8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def gif_animation_pdf(tmp_path_factory, gif_animation_img, request): + tmpdir = tmp_path_factory.mktemp("gif_animation_pdf") + out_pdf = tmpdir / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + gif_animation_img, + ] + ) + pdfinfo = subprocess.check_output(["pdfinfo", str(out_pdf)]) + assert re.search("^Pages: +2$", pdfinfo.decode("utf8"), re.MULTILINE) + subprocess.check_call(["pdfseparate", out_pdf, str(tmpdir / "page-%d.pdf")]) + for page in [1, 2]: + gif_animation_pdf_nr = tmpdir / ("page-%d.pdf" % page) + with pikepdf.open(gif_animation_pdf_nr) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + gif_animation_pdf_nr.unlink() + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_cmyk8_pdf(tmp_path_factory, tiff_cmyk8_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_cmyk8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_cmyk8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceCMYK" + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_rgb8_pdf(tmp_path_factory, tiff_rgb8_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_rgb8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_rgb8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_gray1_pdf(tmp_path_factory, tiff_gray1_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_gray1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_gray1_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == True + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_gray2_pdf(tmp_path_factory, tiff_gray2_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_gray2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_gray2_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_gray4_pdf(tmp_path_factory, tiff_gray4_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_gray4_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_gray4_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_gray8_pdf(tmp_path_factory, tiff_gray8_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_gray8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_gray8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_multipage_pdf(tmp_path_factory, tiff_multipage_img, request): + tmpdir = tmp_path_factory.mktemp("tiff_multipage_pdf") + out_pdf = tmpdir / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_multipage_img, + ] + ) + pdfinfo = subprocess.check_output(["pdfinfo", str(out_pdf)]) + assert re.search("^Pages: +2$", pdfinfo.decode("utf8"), re.MULTILINE) + subprocess.check_call(["pdfseparate", out_pdf, str(tmpdir / "page-%d.pdf")]) + for page in [1, 2]: + tiff_multipage_pdf_nr = tmpdir / ("page-%d.pdf" % page) + with pikepdf.open(tiff_multipage_pdf_nr) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + tiff_multipage_pdf_nr.unlink() + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def tiff_palette1_pdf(tmp_path_factory, tiff_palette1_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_palette1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_palette1_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def tiff_palette2_pdf(tmp_path_factory, tiff_palette2_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_palette2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_palette2_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 2 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def tiff_palette4_pdf(tmp_path_factory, tiff_palette4_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_palette4_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_palette4_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 4 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def tiff_palette8_pdf(tmp_path_factory, tiff_palette8_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_palette8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_palette8_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace[0] == "/Indexed" + assert p.pages[0].Resources.XObject.Im0.ColorSpace[1] == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_lsb_m2l_white_pdf( + tmp_path_factory, tiff_ccitt_lsb_m2l_white_img, request +): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_white_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_lsb_m2l_white_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == False + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_msb_m2l_white_pdf( + tmp_path_factory, tiff_ccitt_msb_m2l_white_img, request +): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_msb_m2l_white_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_msb_m2l_white_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == False + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_msb_l2m_white_pdf( + tmp_path_factory, tiff_ccitt_msb_l2m_white_img, request +): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_msb_l2m_white_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_msb_l2m_white_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == False + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_lsb_m2l_black_pdf( + tmp_path_factory, tiff_ccitt_lsb_m2l_black_img, request +): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_black_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_lsb_m2l_black_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == True + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_nometa1_pdf(tmp_path_factory, tiff_ccitt_nometa1_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_nometa1_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_nometa1_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == False + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def tiff_ccitt_nometa2_pdf(tmp_path_factory, tiff_ccitt_nometa2_img, request): + out_pdf = tmp_path_factory.mktemp("tiff_ccitt_nometa2_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + tiff_ccitt_nometa2_img, + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 1 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].BlackIs1 == False + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Columns == 60 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].K == -1 + assert p.pages[0].Resources.XObject.Im0.DecodeParms[0].Rows == 60 + assert p.pages[0].Resources.XObject.Im0.Filter[0] == "/CCITTFaxDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + yield out_pdf + out_pdf.unlink() + + +############################################################################### +# TEST CASES # +############################################################################### + + +def test_jpg(tmp_path_factory, jpg_img, jpg_pdf): + tmpdir = tmp_path_factory.mktemp("jpg") + pnm = tmpdir / "jpg.pnm" + # We have to use jpegtopnm with the original JPG before being able to compare + # it with imagemagick because imagemagick will decode the JPG slightly + # differently than ghostscript, poppler and mupdf do it. + # We have to use jpegtopnm and cannot use djpeg because the latter produces + # slightly different results as well when called like this: + # djpeg -dct int -pnm "$tempdir/normal.jpg" > "$tempdir/normal.pnm" + # An alternative way to compare the JPG would be to require a different DCT + # method when decoding by setting -define jpeg:dct-method=ifast in the + # compare command. + pnm.write_bytes(subprocess.check_output(["jpegtopnm", "-dct", "int", str(jpg_img)])) + compare_ghostscript(tmpdir, pnm, jpg_pdf) + compare_poppler(tmpdir, pnm, jpg_pdf) + compare_mupdf(tmpdir, pnm, jpg_pdf) + pnm.unlink() + compare_pdfimages_jpg(tmpdir, jpg_img, jpg_pdf) + + +def test_jpg_rot(tmp_path_factory, jpg_rot_img, jpg_rot_pdf): + tmpdir = tmp_path_factory.mktemp("jpg_rot") + # We have to use jpegtopnm with the original JPG before being able to compare + # it with imagemagick because imagemagick will decode the JPG slightly + # differently than ghostscript, poppler and mupdf do it. + # We have to use jpegtopnm and cannot use djpeg because the latter produces + # slightly different results as well when called like this: + # djpeg -dct int -pnm "$tempdir/normal.jpg" > "$tempdir/normal.pnm" + # An alternative way to compare the JPG would be to require a different DCT + # method when decoding by setting -define jpeg:dct-method=ifast in the + # compare command. + jpg_rot_pnm = tmpdir / "jpg_rot.pnm" + jpg_rot_pnm.write_bytes( + subprocess.check_output(["jpegtopnm", "-dct", "int", str(jpg_rot_img)]) + ) + jpg_rot_png = tmpdir / "jpg_rot.png" + subprocess.check_call( + ["convert", "-rotate", "90", str(jpg_rot_pnm), str(jpg_rot_png)] + ) + jpg_rot_pnm.unlink() + compare_ghostscript(tmpdir, jpg_rot_png, jpg_rot_pdf) + compare_poppler(tmpdir, jpg_rot_png, jpg_rot_pdf) + compare_mupdf(tmpdir, jpg_rot_png, jpg_rot_pdf) + jpg_rot_png.unlink() + compare_pdfimages_jpg(tmpdir, jpg_rot_img, jpg_rot_pdf) + + +def test_jpg_cmyk(tmp_path_factory, jpg_cmyk_img, jpg_cmyk_pdf): + tmpdir = tmp_path_factory.mktemp("jpg_cmyk") + compare_ghostscript( + tmpdir, jpg_cmyk_img, jpg_cmyk_pdf, gsdevice="tiff32nc", exact=False + ) + # not testing with poppler as it cannot write CMYK images + compare_mupdf(tmpdir, jpg_cmyk_img, jpg_cmyk_pdf, exact=False, cmyk=True) + compare_pdfimages_jpg(tmpdir, jpg_cmyk_img, jpg_cmyk_pdf) + + +def test_jpg_2000(tmp_path_factory, jpg_2000_img, jpg_2000_pdf): + tmpdir = tmp_path_factory.mktemp("jpg_2000") + compare_ghostscript(tmpdir, jpg_2000_img, jpg_2000_pdf) + compare_poppler(tmpdir, jpg_2000_img, jpg_2000_pdf) + compare_mupdf(tmpdir, jpg_2000_img, jpg_2000_pdf) + compare_pdfimages_jp2(tmpdir, jpg_2000_img, jpg_2000_pdf) + + +def test_png_rgb8(tmp_path_factory, png_rgb8_img, png_rgb8_pdf): + tmpdir = tmp_path_factory.mktemp("png_rgb8") + compare_ghostscript(tmpdir, png_rgb8_img, png_rgb8_pdf) + compare_poppler(tmpdir, png_rgb8_img, png_rgb8_pdf) + compare_mupdf(tmpdir, png_rgb8_img, png_rgb8_pdf) + compare_pdfimages_png(tmpdir, png_rgb8_img, png_rgb8_pdf) + + +def test_png_rgb16(tmp_path_factory, png_rgb16_img, png_rgb16_pdf): + tmpdir = tmp_path_factory.mktemp("png_rgb16") + compare_ghostscript(tmpdir, png_rgb16_img, png_rgb16_pdf, gsdevice="tiff48nc") + # poppler outputs 8-bit RGB so the comparison will not be exact + compare_poppler(tmpdir, png_rgb16_img, png_rgb16_pdf, exact=False) + # pdfimages is unable to write 16 bit output + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_png_rgba8(tmp_path_factory, png_rgba8_img, engine): + out_pdf = tmp_path_factory.mktemp("png_rgba8") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + png_rgba8_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_png_rgba16(tmp_path_factory, png_rgba16_img, engine): + out_pdf = tmp_path_factory.mktemp("png_rgba16") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + png_rgba16_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_png_gray8a(tmp_path_factory, png_gray8a_img, engine): + out_pdf = tmp_path_factory.mktemp("png_gray8a") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + png_gray8a_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_png_gray16a(tmp_path_factory, png_gray16a_img, engine): + out_pdf = tmp_path_factory.mktemp("png_gray16a") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + png_gray16a_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_png_interlaced(tmp_path_factory, png_interlaced_img, png_interlaced_pdf): + tmpdir = tmp_path_factory.mktemp("png_interlaced") + compare_ghostscript(tmpdir, png_interlaced_img, png_interlaced_pdf) + compare_poppler(tmpdir, png_interlaced_img, png_interlaced_pdf) + compare_mupdf(tmpdir, png_interlaced_img, png_interlaced_pdf) + compare_pdfimages_png(tmpdir, png_interlaced_img, png_interlaced_pdf) + + +def test_png_gray1(tmp_path_factory, png_gray1_img, png_gray1_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray1") + compare_ghostscript(tmpdir, png_gray1_img, png_gray1_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, png_gray1_img, png_gray1_pdf) + compare_mupdf(tmpdir, png_gray1_img, png_gray1_pdf) + compare_pdfimages_png(tmpdir, png_gray1_img, png_gray1_pdf) + + +def test_png_gray2(tmp_path_factory, png_gray2_img, png_gray2_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray2") + compare_ghostscript(tmpdir, png_gray2_img, png_gray2_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, png_gray2_img, png_gray2_pdf) + compare_mupdf(tmpdir, png_gray2_img, png_gray2_pdf) + compare_pdfimages_png(tmpdir, png_gray2_img, png_gray2_pdf) + + +def test_png_gray4(tmp_path_factory, png_gray4_img, png_gray4_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray4") + compare_ghostscript(tmpdir, png_gray4_img, png_gray4_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, png_gray4_img, png_gray4_pdf) + compare_mupdf(tmpdir, png_gray4_img, png_gray4_pdf) + compare_pdfimages_png(tmpdir, png_gray4_img, png_gray4_pdf) + + +def test_png_gray8(tmp_path_factory, png_gray8_img, png_gray8_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray8") + compare_ghostscript(tmpdir, png_gray8_img, png_gray8_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, png_gray8_img, png_gray8_pdf) + compare_mupdf(tmpdir, png_gray8_img, png_gray8_pdf) + compare_pdfimages_png(tmpdir, png_gray8_img, png_gray8_pdf) + + +def test_png_gray16(tmp_path_factory, png_gray16_img, png_gray16_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray16") + # ghostscript outputs 8-bit grayscale, so the comparison will not be exact + compare_ghostscript( + tmpdir, png_gray16_img, png_gray16_pdf, gsdevice="pnggray", exact=False + ) + # poppler outputs 8-bit grayscale so the comparison will not be exact + compare_poppler(tmpdir, png_gray16_img, png_gray16_pdf, exact=False) + # pdfimages outputs 8-bit grayscale so the comparison will not be exact + compare_pdfimages_png(tmpdir, png_gray16_img, png_gray16_pdf, exact=False) + + +def test_png_palette1(tmp_path_factory, png_palette1_img, png_palette1_pdf): + tmpdir = tmp_path_factory.mktemp("png_palette1") + compare_ghostscript(tmpdir, png_palette1_img, png_palette1_pdf) + compare_poppler(tmpdir, png_palette1_img, png_palette1_pdf) + compare_mupdf(tmpdir, png_palette1_img, png_palette1_pdf) + # pdfimages cannot export palette based images + + +def test_png_palette2(tmp_path_factory, png_palette2_img, png_palette2_pdf): + tmpdir = tmp_path_factory.mktemp("png_palette2") + compare_ghostscript(tmpdir, png_palette2_img, png_palette2_pdf) + compare_poppler(tmpdir, png_palette2_img, png_palette2_pdf) + compare_mupdf(tmpdir, png_palette2_img, png_palette2_pdf) + # pdfimages cannot export palette based images + + +def test_png_palette4(tmp_path_factory, png_palette4_img, png_palette4_pdf): + tmpdir = tmp_path_factory.mktemp("png_palette4") + compare_ghostscript(tmpdir, png_palette4_img, png_palette4_pdf) + compare_poppler(tmpdir, png_palette4_img, png_palette4_pdf) + compare_mupdf(tmpdir, png_palette4_img, png_palette4_pdf) + # pdfimages cannot export palette based images + + +def test_png_palette8(tmp_path_factory, png_palette8_img, png_palette8_pdf): + tmpdir = tmp_path_factory.mktemp("png_palette8") + compare_ghostscript(tmpdir, png_palette8_img, png_palette8_pdf) + compare_poppler(tmpdir, png_palette8_img, png_palette8_pdf) + compare_mupdf(tmpdir, png_palette8_img, png_palette8_pdf) + # pdfimages cannot export palette based images + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_gif_transparent(tmp_path_factory, gif_transparent_img, engine): + out_pdf = tmp_path_factory.mktemp("gif_transparent") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + gif_transparent_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_gif_palette1(tmp_path_factory, gif_palette1_img, gif_palette1_pdf): + tmpdir = tmp_path_factory.mktemp("gif_palette1") + compare_ghostscript(tmpdir, gif_palette1_img, gif_palette1_pdf) + compare_poppler(tmpdir, gif_palette1_img, gif_palette1_pdf) + compare_mupdf(tmpdir, gif_palette1_img, gif_palette1_pdf) + # pdfimages cannot export palette based images + + +def test_gif_palette2(tmp_path_factory, gif_palette2_img, gif_palette2_pdf): + tmpdir = tmp_path_factory.mktemp("gif_palette2") + compare_ghostscript(tmpdir, gif_palette2_img, gif_palette2_pdf) + compare_poppler(tmpdir, gif_palette2_img, gif_palette2_pdf) + compare_mupdf(tmpdir, gif_palette2_img, gif_palette2_pdf) + # pdfimages cannot export palette based images + + +def test_gif_palette4(tmp_path_factory, gif_palette4_img, gif_palette4_pdf): + tmpdir = tmp_path_factory.mktemp("gif_palette4") + compare_ghostscript(tmpdir, gif_palette4_img, gif_palette4_pdf) + compare_poppler(tmpdir, gif_palette4_img, gif_palette4_pdf) + compare_mupdf(tmpdir, gif_palette4_img, gif_palette4_pdf) + # pdfimages cannot export palette based images + + +def test_gif_palette8(tmp_path_factory, gif_palette8_img, gif_palette8_pdf): + tmpdir = tmp_path_factory.mktemp("gif_palette8") + compare_ghostscript(tmpdir, gif_palette8_img, gif_palette8_pdf) + compare_poppler(tmpdir, gif_palette8_img, gif_palette8_pdf) + compare_mupdf(tmpdir, gif_palette8_img, gif_palette8_pdf) + # pdfimages cannot export palette based images + + +def test_gif_animation(tmp_path_factory, gif_animation_img, gif_animation_pdf): + tmpdir = tmp_path_factory.mktemp("gif_animation") + subprocess.check_call( + ["pdfseparate", gif_animation_pdf, str(tmpdir / "page-%d.pdf")] + ) + for page in [1, 2]: + gif_animation_pdf_nr = tmpdir / ("page-%d.pdf" % page) + compare_ghostscript( + tmpdir, str(gif_animation_img) + "[%d]" % (page - 1), gif_animation_pdf_nr + ) + compare_poppler( + tmpdir, str(gif_animation_img) + "[%d]" % (page - 1), gif_animation_pdf_nr + ) + compare_mupdf( + tmpdir, str(gif_animation_img) + "[%d]" % (page - 1), gif_animation_pdf_nr + ) + # pdfimages cannot export palette based images + gif_animation_pdf_nr.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_float(tmp_path_factory, tiff_float_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_float") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_float_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_tiff_cmyk8(tmp_path_factory, tiff_cmyk8_img, tiff_cmyk8_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_cmyk8") + compare_ghostscript( + tmpdir, tiff_cmyk8_img, tiff_cmyk8_pdf, gsdevice="tiff32nc", exact=False + ) + # not testing with poppler as it cannot write CMYK images + compare_mupdf(tmpdir, tiff_cmyk8_img, tiff_cmyk8_pdf, exact=False, cmyk=True) + compare_pdfimages_tiff(tmpdir, tiff_cmyk8_img, tiff_cmyk8_pdf) + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_cmyk16(tmp_path_factory, tiff_cmyk16_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_cmyk16") / "out.pdf" + # PIL is unable to read 16 bit CMYK images + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_cmyk16_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_tiff_rgb8(tmp_path_factory, tiff_rgb8_img, tiff_rgb8_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_rgb8") + compare_ghostscript(tmpdir, tiff_rgb8_img, tiff_rgb8_pdf, gsdevice="tiff24nc") + compare_poppler(tmpdir, tiff_rgb8_img, tiff_rgb8_pdf) + compare_mupdf(tmpdir, tiff_rgb8_img, tiff_rgb8_pdf) + compare_pdfimages_tiff(tmpdir, tiff_rgb8_img, tiff_rgb8_pdf) + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_rgb12(tmp_path_factory, tiff_rgb12_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_rgb12") / "out.pdf" + # PIL is unable to preserve more than 8 bits per sample + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_rgb12_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_rgb14(tmp_path_factory, tiff_rgb14_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_rgb14") / "out.pdf" + # PIL is unable to preserve more than 8 bits per sample + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_rgb14_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_rgb16(tmp_path_factory, tiff_rgb16_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_rgb16") / "out.pdf" + # PIL is unable to preserve more than 8 bits per sample + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_rgb16_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_rgba8(tmp_path_factory, tiff_rgba8_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_rgba8") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_rgba8_img, + ] + ).returncode + ) + out_pdf.unlink() + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_rgba16(tmp_path_factory, tiff_rgba16_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_rgba16") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_rgba16_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_tiff_gray1(tmp_path_factory, tiff_gray1_img, tiff_gray1_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_gray1") + compare_ghostscript(tmpdir, tiff_gray1_img, tiff_gray1_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, tiff_gray1_img, tiff_gray1_pdf) + compare_mupdf(tmpdir, tiff_gray1_img, tiff_gray1_pdf) + compare_pdfimages_tiff(tmpdir, tiff_gray1_img, tiff_gray1_pdf) + + +def test_tiff_gray2(tmp_path_factory, tiff_gray2_img, tiff_gray2_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_gray2") + compare_ghostscript(tmpdir, tiff_gray2_img, tiff_gray2_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, tiff_gray2_img, tiff_gray2_pdf) + compare_mupdf(tmpdir, tiff_gray2_img, tiff_gray2_pdf) + compare_pdfimages_tiff(tmpdir, tiff_gray2_img, tiff_gray2_pdf) + + +def test_tiff_gray4(tmp_path_factory, tiff_gray4_img, tiff_gray4_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_gray4") + compare_ghostscript(tmpdir, tiff_gray4_img, tiff_gray4_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, tiff_gray4_img, tiff_gray4_pdf) + compare_mupdf(tmpdir, tiff_gray4_img, tiff_gray4_pdf) + compare_pdfimages_tiff(tmpdir, tiff_gray4_img, tiff_gray4_pdf) + + +def test_tiff_gray8(tmp_path_factory, tiff_gray8_img, tiff_gray8_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_gray8") + compare_ghostscript(tmpdir, tiff_gray8_img, tiff_gray8_pdf, gsdevice="pnggray") + compare_poppler(tmpdir, tiff_gray8_img, tiff_gray8_pdf) + compare_mupdf(tmpdir, tiff_gray8_img, tiff_gray8_pdf) + compare_pdfimages_tiff(tmpdir, tiff_gray8_img, tiff_gray8_pdf) + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_tiff_gray16(tmp_path_factory, tiff_gray16_img, engine): + out_pdf = tmp_path_factory.mktemp("tiff_gray16") / "out.pdf" + assert ( + 0 + != subprocess.run( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + engine, + "--output=" + str(out_pdf), + tiff_gray16_img, + ] + ).returncode + ) + out_pdf.unlink() + + +def test_tiff_multipage(tmp_path_factory, tiff_multipage_img, tiff_multipage_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_multipage") + subprocess.check_call( + ["pdfseparate", tiff_multipage_pdf, str(tmpdir / "page-%d.pdf")] + ) + for page in [1, 2]: + tiff_multipage_pdf_nr = tmpdir / ("page-%d.pdf" % page) + compare_ghostscript( + tmpdir, str(tiff_multipage_img) + "[%d]" % (page - 1), tiff_multipage_pdf_nr + ) + compare_poppler( + tmpdir, str(tiff_multipage_img) + "[%d]" % (page - 1), tiff_multipage_pdf_nr + ) + compare_mupdf( + tmpdir, str(tiff_multipage_img) + "[%d]" % (page - 1), tiff_multipage_pdf_nr + ) + compare_pdfimages_tiff( + tmpdir, str(tiff_multipage_img) + "[%d]" % (page - 1), tiff_multipage_pdf_nr + ) + tiff_multipage_pdf_nr.unlink() + + +def test_tiff_palette1(tmp_path_factory, tiff_palette1_img, tiff_palette1_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_palette1") + compare_ghostscript(tmpdir, tiff_palette1_img, tiff_palette1_pdf) + compare_poppler(tmpdir, tiff_palette1_img, tiff_palette1_pdf) + compare_mupdf(tmpdir, tiff_palette1_img, tiff_palette1_pdf) + # pdfimages cannot export palette based images + + +def test_tiff_palette2(tmp_path_factory, tiff_palette2_img, tiff_palette2_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_palette2") + compare_ghostscript(tmpdir, tiff_palette2_img, tiff_palette2_pdf) + compare_poppler(tmpdir, tiff_palette2_img, tiff_palette2_pdf) + compare_mupdf(tmpdir, tiff_palette2_img, tiff_palette2_pdf) + # pdfimages cannot export palette based images + + +def test_tiff_palette4(tmp_path_factory, tiff_palette4_img, tiff_palette4_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_palette4") + compare_ghostscript(tmpdir, tiff_palette4_img, tiff_palette4_pdf) + compare_poppler(tmpdir, tiff_palette4_img, tiff_palette4_pdf) + compare_mupdf(tmpdir, tiff_palette4_img, tiff_palette4_pdf) + # pdfimages cannot export palette based images + + +def test_tiff_palette8(tmp_path_factory, tiff_palette8_img, tiff_palette8_pdf): + tmpdir = tmp_path_factory.mktemp("tiff_palette8") + compare_ghostscript(tmpdir, tiff_palette8_img, tiff_palette8_pdf) + compare_poppler(tmpdir, tiff_palette8_img, tiff_palette8_pdf) + compare_mupdf(tmpdir, tiff_palette8_img, tiff_palette8_pdf) + # pdfimages cannot export palette based images + + +def test_tiff_ccitt_lsb_m2l_white( + tmp_path_factory, tiff_ccitt_lsb_m2l_white_img, tiff_ccitt_lsb_m2l_white_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_white") + compare_ghostscript( + tmpdir, + tiff_ccitt_lsb_m2l_white_img, + tiff_ccitt_lsb_m2l_white_pdf, + gsdevice="pnggray", + ) + compare_poppler(tmpdir, tiff_ccitt_lsb_m2l_white_img, tiff_ccitt_lsb_m2l_white_pdf) + compare_mupdf(tmpdir, tiff_ccitt_lsb_m2l_white_img, tiff_ccitt_lsb_m2l_white_pdf) + compare_pdfimages_tiff( + tmpdir, tiff_ccitt_lsb_m2l_white_img, tiff_ccitt_lsb_m2l_white_pdf + ) + + +def test_tiff_ccitt_msb_m2l_white( + tmp_path_factory, tiff_ccitt_msb_m2l_white_img, tiff_ccitt_msb_m2l_white_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_msb_m2l_white") + compare_ghostscript( + tmpdir, + tiff_ccitt_msb_m2l_white_img, + tiff_ccitt_msb_m2l_white_pdf, + gsdevice="pnggray", + ) + compare_poppler(tmpdir, tiff_ccitt_msb_m2l_white_img, tiff_ccitt_msb_m2l_white_pdf) + compare_mupdf(tmpdir, tiff_ccitt_msb_m2l_white_img, tiff_ccitt_msb_m2l_white_pdf) + compare_pdfimages_tiff( + tmpdir, tiff_ccitt_msb_m2l_white_img, tiff_ccitt_msb_m2l_white_pdf + ) + + +def test_tiff_ccitt_msb_l2m_white( + tmp_path_factory, tiff_ccitt_msb_l2m_white_img, tiff_ccitt_msb_l2m_white_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_msb_l2m_white") + compare_ghostscript( + tmpdir, + tiff_ccitt_msb_l2m_white_img, + tiff_ccitt_msb_l2m_white_pdf, + gsdevice="pnggray", + ) + compare_poppler(tmpdir, tiff_ccitt_msb_l2m_white_img, tiff_ccitt_msb_l2m_white_pdf) + compare_mupdf(tmpdir, tiff_ccitt_msb_l2m_white_img, tiff_ccitt_msb_l2m_white_pdf) + compare_pdfimages_tiff( + tmpdir, tiff_ccitt_msb_l2m_white_img, tiff_ccitt_msb_l2m_white_pdf + ) + + +def test_tiff_ccitt_lsb_m2l_black( + tmp_path_factory, tiff_ccitt_lsb_m2l_black_img, tiff_ccitt_lsb_m2l_black_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_lsb_m2l_black") + compare_ghostscript( + tmpdir, + tiff_ccitt_lsb_m2l_black_img, + tiff_ccitt_lsb_m2l_black_pdf, + gsdevice="pnggray", + ) + compare_poppler(tmpdir, tiff_ccitt_lsb_m2l_black_img, tiff_ccitt_lsb_m2l_black_pdf) + compare_mupdf(tmpdir, tiff_ccitt_lsb_m2l_black_img, tiff_ccitt_lsb_m2l_black_pdf) + compare_pdfimages_tiff( + tmpdir, tiff_ccitt_lsb_m2l_black_img, tiff_ccitt_lsb_m2l_black_pdf + ) + + +def test_tiff_ccitt_nometa1( + tmp_path_factory, tiff_ccitt_nometa1_img, tiff_ccitt_nometa1_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_nometa1") + compare_ghostscript( + tmpdir, tiff_ccitt_nometa1_img, tiff_ccitt_nometa1_pdf, gsdevice="pnggray" + ) + compare_poppler(tmpdir, tiff_ccitt_nometa1_img, tiff_ccitt_nometa1_pdf) + compare_mupdf(tmpdir, tiff_ccitt_nometa1_img, tiff_ccitt_nometa1_pdf) + compare_pdfimages_tiff(tmpdir, tiff_ccitt_nometa1_img, tiff_ccitt_nometa1_pdf) + + +def test_tiff_ccitt_nometa2( + tmp_path_factory, tiff_ccitt_nometa2_img, tiff_ccitt_nometa2_pdf +): + tmpdir = tmp_path_factory.mktemp("tiff_ccitt_nometa2") + compare_ghostscript( + tmpdir, tiff_ccitt_nometa2_img, tiff_ccitt_nometa2_pdf, gsdevice="pnggray" + ) + compare_poppler(tmpdir, tiff_ccitt_nometa2_img, tiff_ccitt_nometa2_pdf) + compare_mupdf(tmpdir, tiff_ccitt_nometa2_img, tiff_ccitt_nometa2_pdf) + compare_pdfimages_tiff(tmpdir, tiff_ccitt_nometa2_img, tiff_ccitt_nometa2_pdf) + + +# 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 +poster = (97200, 50400) +# 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 + + +@pytest.mark.parametrize( + "layout_test_cases", + [ + # fmt: off + # 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)), + (poster, None, None, f_fill, 0, (97200, 50400), (151200, 50400), + (97200, 50400), (100800, 50400)), + ] + # fmt: on +) +def test_layout(layout_test_cases): + # 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 + psopt, isopt, border, fit, ao, pspdf1, ispdf1, pspdf2, ispdf2 = layout_test_cases + if isopt is not None: + isopt = ((img2pdf.ImgSize.abs, isopt[0]), (img2pdf.ImgSize.abs, isopt[1])) + layout_fun = img2pdf.get_layout_fun(psopt, isopt, border, fit, ao) + try: + pwpdf, phpdf, iwpdf, ihpdf = layout_fun( + im1[0], im1[1], (img2pdf.default_dpi, img2pdf.default_dpi) + ) + assert (pwpdf, phpdf) == pspdf1 + assert (iwpdf, ihpdf) == ispdf1 + except img2pdf.NegativeDimensionError: + assert pspdf1 is None + assert ispdf1 is None + try: + pwpdf, phpdf, iwpdf, ihpdf = layout_fun( + im2[0], im2[1], (img2pdf.default_dpi, img2pdf.default_dpi) + ) + assert (pwpdf, phpdf) == pspdf2 + assert (iwpdf, ihpdf) == ispdf2 + except img2pdf.NegativeDimensionError: + assert pspdf2 is None + assert ispdf2 is None + + +@pytest.fixture( + scope="session", + params=os.listdir(os.path.join(os.path.dirname(__file__), "src", "tests", "input")), +) +def general_input(request): + assert os.path.isfile( + os.path.join(os.path.dirname(__file__), "src", "tests", "input", request.param) + ) + return request.param + + +@pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) +def test_general(general_input, engine): + inputf = os.path.join( + os.path.dirname(__file__), "src", "tests", "input", general_input + ) + outputf = os.path.join( + os.path.dirname(__file__), "src", "tests", "output", general_input + ".pdf" + ) + assert os.path.isfile(outputf) + f = inputf + out = outputf + + engine = getattr(img2pdf.Engine, engine) + + # we do not test animation.gif with pdfrw because it doesn't support + # saving hexadecimal palette data + if f.endswith(os.path.sep + "animation.gif") and engine == img2pdf.Engine.pdfrw: + return + with open(f, "rb") as inf: + orig_imgdata = inf.read() + output = img2pdf.convert(orig_imgdata, nodate=True, engine=engine) + x = pikepdf.open(BytesIO(output)) + assert x.Root.Pages.Count in (1, 2) + if len(x.Root.Pages.Kids) == "1": + assert x.Size == "7" + assert len(x.Root.Pages.Kids) == 1 + elif len(x.Root.Pages.Kids) == "2": + assert x.Size == "10" + assert len(x.Root.Pages.Kids) == 2 + assert sorted(x.Root.keys()) == ["/Pages", "/Type"] + assert x.Root.Type == "/Catalog" + assert sorted(x.Root.Pages.keys()) == ["/Count", "/Kids", "/Type"] + assert x.Root.Pages.Type == "/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 int(f) + else: + return decimal.Decimal("%.4f" % f) + + assert sorted(cur_page.keys()) == [ + "/Contents", + "/MediaBox", + "/Parent", + "/Resources", + "/Type", + ] + assert cur_page.MediaBox == pikepdf.Array( + [0, 0, format_float(pagewidth), format_float(pageheight)] + ) + assert cur_page.Parent == x.Root.Pages + assert cur_page.Type == "/Page" + assert cur_page.Resources.keys() == {"/XObject"} + assert cur_page.Resources.XObject.keys() == {"/Im0"} + if engine != img2pdf.Engine.pikepdf: + assert cur_page.Contents.Length == len(cur_page.Contents.read_bytes()) + assert cur_page.Contents.read_bytes() == b"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: + assert imgprops.Filter in [ + "/DCTDecode", + "/JPXDecode", + "/FlateDecode", + pikepdf.Array([pikepdf.Name.CCITTFaxDecode]), + ] + + # test if the image has correct size + assert imgprops.Width == orig_img.size[0] + assert imgprops.Height == 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 ["/DCTDecode", "/JPXDecode"]: + assert cur_page.Resources.XObject.Im0.read_raw_bytes() == orig_imgdata + elif imgprops.Filter == pikepdf.Array([pikepdf.Name.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(cur_page.Resources.XObject.Im0.read_raw_bytes()) + imgio.seek(0) + im = Image.open(imgio) + assert im.tobytes() == orig_img.tobytes() + try: + im.close() + except AttributeError: + pass + elif imgprops.Filter == "/FlateDecode": + # otherwise, the data is flate encoded and has to be equal + # to the pixel data of the input image + imgdata = zlib.decompress(cur_page.Resources.XObject.Im0.read_raw_bytes()) + if hasattr(imgprops, "DecodeParms"): + if orig_img.format == "PNG": + pngidat, palette = img2pdf.parse_png(orig_imgdata) + elif ( + orig_img.format == "TIFF" + and orig_img.info["compression"] == "group4" + ): + offset, length = img2pdf.ccitt_payload_location_from_pil(orig_img) + pngidat = orig_imgdata[offset : offset + length] + else: + pngbuffer = BytesIO() + orig_img.save(pngbuffer, format="png") + pngidat, palette = img2pdf.parse_png(pngbuffer.getvalue()) + assert zlib.decompress(pngidat) == imgdata + else: + colorspace = imgprops.ColorSpace + if colorspace == "/DeviceGray": + colorspace = "L" + elif colorspace == "/DeviceRGB": + colorspace = "RGB" + elif colorspace == "/DeviceCMYK": + colorspace = "CMYK" + else: + raise Exception("invalid colorspace") + im = Image.frombytes( + colorspace, (int(imgprops.Width), int(imgprops.Height)), imgdata + ) + if orig_img.mode == "1": + assert im.tobytes() == orig_img.convert("L").tobytes() + elif orig_img.mode not in ("RGB", "L", "CMYK", "CMYK;I"): + assert 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 + else: + raise Exception("unknown filter") + + def rec(obj): + if isinstance(obj, pikepdf.Dictionary): + return {k: rec(v) for k, v in obj.items() if k != "/Parent"} + elif isinstance(obj, pikepdf.Array): + return [rec(v) for v in obj] + elif isinstance(obj, pikepdf.Stream): + ret = rec(obj.stream_dict) + stream = obj.read_raw_bytes() + assert len(stream) == ret["/Length"] + del ret["/Length"] + if ret.get("/Filter") == "/FlateDecode": + stream = obj.read_bytes() + del ret["/Filter"] + ret["stream"] = stream + return ret + elif isinstance(obj, pikepdf.Name) or isinstance(obj, pikepdf.String): + return str(obj) + elif isinstance(obj, decimal.Decimal) or isinstance(obj, str): + return obj + elif isinstance(obj, int): + return decimal.Decimal(obj) + raise Exception("unhandled: %s" % (type(obj))) + + y = pikepdf.open(out) + assert rec(x.Root) == rec(y.Root) + # the python-pil version 2.3.0-1ubuntu3 in Ubuntu does not have the + # close() method + try: + orig_img.close() + except AttributeError: + pass