diff --git a/src/img2pdf.py b/src/img2pdf.py index a237b63..1954d66 100755 --- a/src/img2pdf.py +++ b/src/img2pdf.py @@ -1664,12 +1664,21 @@ def parse_miff(data): hdata["rows"], lenimgdata, ) + if colorspace == Colorspace.RGB and hdata["depth"] == 8: + newimg = Image.frombytes("RGB", (hdata["columns"], hdata["rows"]), rest[:lenimgdata]) + imgdata, palette, depth = to_png_data(newimg) + assert palette == b"" + assert depth == hdata["depth"] + imgfmt = ImageFormat.PNG + else: + imgdata = zlib.compress(rest[:lenimgdata]) + imgfmt = ImageFormat.MIFF results.append( ( colorspace, hdata.get("resolution") or (default_dpi, default_dpi), - ImageFormat.MIFF, - zlib.compress(rest[:lenimgdata]), + imgfmt, + imgdata, None, # smask hdata["columns"], hdata["rows"], diff --git a/src/img2pdf_test.py b/src/img2pdf_test.py index 519bb85..31b4043 100755 --- a/src/img2pdf_test.py +++ b/src/img2pdf_test.py @@ -291,7 +291,7 @@ def write_png(data, path, bitdepth, colortype, palette=None, iccp=None): for j in range(valsperbyte): if x + j >= data.shape[1]: break - val |= (data[y, x + j].astype(">u2") & (2 ** bitdepth - 1)) << ( + val |= (data[y, x + j].astype(">u2") & (2**bitdepth - 1)) << ( (valsperbyte - j - 1) * bitdepth ) raw += struct.pack(">B", val) @@ -3875,6 +3875,53 @@ def tiff_ccitt_nometa2_img(tmp_path_factory, tmp_gray1_png): yield in_img in_img.unlink() + +@pytest.fixture(scope="session") +def miff_cmyk8_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("miff_cmyk8") / "in.miff" + subprocess.check_call( + CONVERT + + [ + str(tmp_normal_png), + "-colorspace", + "cmyk", + str(in_img), + ] + ) + identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"])) + assert len(identify) == 1 + # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was + # put into an array, here we cater for the older version containing just + # the bare dictionary + if "image" in identify: + identify = [identify] + assert "image" in identify[0] + assert identify[0]["image"].get("format") == "MIFF", str(identify) + assert identify[0]["image"].get("class") == "DirectClass" + assert identify[0]["image"].get("baseType") == "ColorSeparation" + assert identify[0]["image"].get("geometry") == { + "width": 60, + "height": 60, + "x": 0, + "y": 0, + }, str(identify) + assert identify[0]["image"].get("colorspace") == "CMYK", str(identify) + assert identify[0]["image"].get("type") == "ColorSeparation", str(identify) + endian = "endianess" if identify[0].get("version", "0") < "1.0" else "endianness" + assert identify[0]["image"].get(endian) in ["Undefined", "LSB",], str( + identify + ) # FIXME: should be LSB + assert identify[0]["image"].get("depth") == 8, str(identify) + assert identify[0]["image"].get("pageGeometry") == { + "width": 60, + "height": 60, + "x": 0, + "y": 0, + }, str(identify) + yield in_img + in_img.unlink() + + @pytest.fixture(scope="session") def miff_cmyk16_img(tmp_path_factory, tmp_normal_png): in_img = tmp_path_factory.mktemp("miff_cmyk16") / "in.miff" @@ -3898,6 +3945,8 @@ def miff_cmyk16_img(tmp_path_factory, tmp_normal_png): identify = [identify] assert "image" in identify[0] assert identify[0]["image"].get("format") == "MIFF", str(identify) + assert identify[0]["image"].get("class") == "DirectClass" + assert identify[0]["image"].get("baseType") == "ColorSeparation" assert identify[0]["image"].get("geometry") == { "width": 60, "height": 60, @@ -3921,6 +3970,45 @@ def miff_cmyk16_img(tmp_path_factory, tmp_normal_png): yield in_img in_img.unlink() + +@pytest.fixture(scope="session") +def miff_rgb8_img(tmp_path_factory, tmp_normal_png): + in_img = tmp_path_factory.mktemp("miff_rgb8") / "in.miff" + subprocess.check_call(CONVERT + [str(tmp_normal_png), str(in_img)]) + identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"])) + assert len(identify) == 1 + # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was + # put into an array, here we cater for the older version containing just + # the bare dictionary + if "image" in identify: + identify = [identify] + assert "image" in identify[0] + assert identify[0]["image"].get("format") == "MIFF", str(identify) + assert identify[0]["image"].get("class") == "DirectClass" + assert identify[0]["image"].get("baseType") == "TrueColor" + assert identify[0]["image"].get("geometry") == { + "width": 60, + "height": 60, + "x": 0, + "y": 0, + }, str(identify) + assert identify[0]["image"].get("colorspace") == "sRGB", str(identify) + assert identify[0]["image"].get("type") == "TrueColor", str(identify) + endian = "endianess" if identify[0].get("version", "0") < "1.0" else "endianness" + assert identify[0]["image"].get(endian) in ["Undefined", "LSB",], str( + identify + ) # FIXME: should be LSB + assert identify[0]["image"].get("depth") == 8, str(identify) + assert identify[0]["image"].get("pageGeometry") == { + "width": 60, + "height": 60, + "x": 0, + "y": 0, + }, str(identify) + yield in_img + in_img.unlink() + + @pytest.fixture(scope="session") def png_icc_img(tmp_icc_png): in_img = tmp_icc_png @@ -5306,6 +5394,32 @@ def tiff_ccitt_nometa2_pdf(tmp_path_factory, tiff_ccitt_nometa2_img, request): out_pdf.unlink() +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def miff_cmyk8_pdf(tmp_path_factory, miff_cmyk8_img, request): + out_pdf = tmp_path_factory.mktemp("miff_cmyk8_pdf") / "out.pdf" + subprocess.check_call( + [ + img2pdfprog, + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(miff_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"]) def miff_cmyk16_pdf(tmp_path_factory, miff_cmyk16_img, request): @@ -5334,6 +5448,35 @@ def miff_cmyk16_pdf(tmp_path_factory, miff_cmyk16_img, request): out_pdf.unlink() +@pytest.fixture(scope="session", params=["internal", "pikepdf"]) +def miff_rgb8_pdf(tmp_path_factory, miff_rgb8_img, request): + out_pdf = tmp_path_factory.mktemp("miff_rgb8_pdf") / "out.pdf" + subprocess.check_call( + [ + img2pdfprog, + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(miff_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() + ############################################################################### # TEST CASES # @@ -6201,14 +6344,42 @@ def test_tiff_ccitt_nometa2( sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) -def test_miff_cmyk16(tmp_path_factory, miff_cmyk16_img, tiff_cmyk16_img, miff_cmyk16_pdf): +def test_miff_cmyk8(tmp_path_factory, miff_cmyk8_img, tiff_cmyk8_img, miff_cmyk8_pdf): + tmpdir = tmp_path_factory.mktemp("miff_cmyk8") + compare_ghostscript( + tmpdir, tiff_cmyk8_img, miff_cmyk8_pdf, gsdevice="tiff32nc", exact=False + ) + # not testing with poppler as it cannot write CMYK images + compare_mupdf(tmpdir, tiff_cmyk8_img, miff_cmyk8_pdf, exact=False, cmyk=True) + compare_pdfimages_tiff(tmpdir, tiff_cmyk8_img, miff_cmyk8_pdf) + + +@pytest.mark.skipif( + sys.platform in ["win32"], + reason="test utilities not available on Windows and MacOS", +) +def test_miff_cmyk16( + tmp_path_factory, miff_cmyk16_img, tiff_cmyk16_img, miff_cmyk16_pdf +): tmpdir = tmp_path_factory.mktemp("miff_cmyk16") compare_ghostscript( tmpdir, tiff_cmyk16_img, miff_cmyk16_pdf, gsdevice="tiff32nc", exact=False ) # not testing with poppler as it cannot write CMYK images compare_mupdf(tmpdir, tiff_cmyk16_img, miff_cmyk16_pdf, exact=False, cmyk=True) - #compare_pdfimages_tiff(tmpdir, tiff_cmyk16_img, miff_cmyk16_pdf) + # compare_pdfimages_tiff(tmpdir, tiff_cmyk16_img, miff_cmyk16_pdf) + + +@pytest.mark.skipif( + sys.platform in ["win32"], + reason="test utilities not available on Windows and MacOS", +) +def test_miff_rgb8(tmp_path_factory, miff_rgb8_img, tiff_rgb8_img, miff_rgb8_pdf): + tmpdir = tmp_path_factory.mktemp("miff_rgb8") + compare_ghostscript(tmpdir, tiff_rgb8_img, miff_rgb8_pdf, gsdevice="tiff24nc") + compare_poppler(tmpdir, tiff_rgb8_img, miff_rgb8_pdf) + compare_mupdf(tmpdir, tiff_rgb8_img, miff_rgb8_pdf) + compare_pdfimages_tiff(tmpdir, tiff_rgb8_img, miff_rgb8_pdf) # we define some variables so that the table below can be narrower