diff --git a/src/img2pdf.py b/src/img2pdf.py index 8d5f2cf..1dc01c4 100755 --- a/src/img2pdf.py +++ b/src/img2pdf.py @@ -85,9 +85,9 @@ FitMode = Enum("FitMode", "into fill exact shrink enlarge") PageOrientation = Enum("PageOrientation", "portrait landscape") -Colorspace = Enum("Colorspace", "RGB L 1 CMYK CMYK;I RGBA P other") +Colorspace = Enum("Colorspace", "RGB RGBA L LA 1 CMYK CMYK;I P other") -ImageFormat = Enum("ImageFormat", "JPEG JPEG2000 CCITTGroup4 PNG TIFF MPO other") +ImageFormat = Enum("ImageFormat", "JPEG JPEG2000 CCITTGroup4 PNG GIF TIFF MPO other") PageMode = Enum("PageMode", "none outlines thumbs") @@ -742,6 +742,7 @@ class pdfdoc(object): imgheightpx, imgformat, imgdata, + smaskdata, imgwidthpdf, imgheightpdf, imgxpdf, @@ -759,6 +760,11 @@ class pdfdoc(object): artborder=None, iccp=None, ): + assert ( + (color != Colorspace.RGBA and color != Colorspace.LA) + or (imgformat == ImageFormat.PNG and smaskdata is not None) + ) + if self.engine == Engine.pikepdf: PdfArray = pikepdf.Array PdfDict = pikepdf.Dictionary @@ -777,9 +783,9 @@ class pdfdoc(object): TrueObject = True if self.engine == Engine.pikepdf else PdfObject("true") FalseObject = False if self.engine == Engine.pikepdf else PdfObject("false") - if color == Colorspace["1"] or color == Colorspace.L: + if color == Colorspace["1"] or color == Colorspace.L or color == Colorspace.LA: colorspace = PdfName.DeviceGray - elif color == Colorspace.RGB: + elif color == Colorspace.RGB or color == Colorspace.RGBA: colorspace = PdfName.DeviceRGB elif color == Colorspace.CMYK or color == Colorspace["CMYK;I"]: colorspace = PdfName.DeviceCMYK @@ -816,9 +822,9 @@ class pdfdoc(object): else: iccpdict = PdfDict(stream=convert_load(iccp)) iccpdict[PdfName.Alternate] = colorspace - if color == Colorspace["1"] or color == Colorspace.L: + if color == Colorspace["1"] or color == Colorspace.L or color == Colorspace.LA: iccpdict[PdfName.N] = 1 - elif color == Colorspace.RGB: + elif color == Colorspace.RGB or color == Colorspace.RGBA: iccpdict[PdfName.N] = 3 elif color == Colorspace.CMYK or color == Colorspace["CMYK;I"]: iccpdict[PdfName.N] = 4 @@ -852,6 +858,8 @@ class pdfdoc(object): image[PdfName.ColorSpace] = colorspace image[PdfName.BitsPerComponent] = depth + smask = None + if color == Colorspace["CMYK;I"]: # Inverts all four channels image[PdfName.Decode] = [1, 0, 1, 0, 1, 0, 1, 0] @@ -869,9 +877,35 @@ class pdfdoc(object): decodeparms[PdfName.Rows] = imgheightpx image[PdfName.DecodeParms] = [decodeparms] elif imgformat is ImageFormat.PNG: + if smaskdata is not None: + if self.engine == Engine.pikepdf: + smask = self.writer.make_stream(smaskdata) + else: + smask = PdfDict(stream=convert_load(smaskdata)) + smask[PdfName.Type] = PdfName.XObject + smask[PdfName.Subtype] = PdfName.Image + smask[PdfName.Filter] = PdfName.FlateDecode + smask[PdfName.Width] = imgwidthpx + smask[PdfName.Height] = imgheightpx + smask[PdfName.ColorSpace] = PdfName.DeviceGray + smask[PdfName.BitsPerComponent] = depth + + decodeparms = PdfDict() + decodeparms[PdfName.Predictor] = 15 + decodeparms[PdfName.Colors] = 1 + decodeparms[PdfName.Columns] = imgwidthpx + decodeparms[PdfName.BitsPerComponent] = depth + smask[PdfName.DecodeParms] = decodeparms + + image[PdfName.SMask] = smask + + # /SMask requires PDF 1.4 + if self.output_version < "1.4": + self.output_version = "1.4" + decodeparms = PdfDict() decodeparms[PdfName.Predictor] = 15 - if color in [Colorspace.P, Colorspace["1"], Colorspace.L]: + if color in [Colorspace.P, Colorspace["1"], Colorspace.L, Colorspace.LA]: decodeparms[PdfName.Colors] = 1 else: decodeparms[PdfName.Colors] = 3 @@ -954,6 +988,8 @@ class pdfdoc(object): if self.engine == Engine.internal: self.writer.addobj(content) self.writer.addobj(image) + if smask is not None: + self.writer.addobj(smask) if iccp is not None: self.writer.addobj(iccpdict) @@ -1183,8 +1219,21 @@ def get_imgmetadata( # Search online for the 72.009 dpi problem for more info. ndpi = (int(round(ndpi[0])), int(round(ndpi[1]))) ics = imgdata.mode - - if ics in ["LA", "PA", "RGBA"] or "transparency" in imgdata.info: + + # GIF and PNG files with transparency are supported + if ( + (imgformat == ImageFormat.PNG or imgformat == ImageFormat.GIF) + and (ics in ["RGBA", "LA"] or "transparency" in imgdata.info) + ): + # Must check the IHDR chunk for the bit depth, because PIL would lossily + # convert 16-bit RGBA/LA images to 8-bit. + if imgformat == ImageFormat.PNG and rawdata is not None: + depth = rawdata[24] + if depth > 8: + logger.warning("Image with transparency and a bit depth of %d." % depth) + logger.warning("This is unsupported due to PIL limitations.") + raise AlphaChannelError("Refusing to work with multiple >8bit channels.") + elif (ics in ["LA", "PA", "RGBA"] or "transparency" in imgdata.info): logger.warning("Image contains transparency which cannot be retained in PDF.") logger.warning("img2pdf will not perform a lossy operation.") logger.warning("You can remove the alpha channel using imagemagick:") @@ -1427,6 +1476,7 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ndpi, imgformat, rawdata, + None, imgwidthpx, imgheightpx, [], @@ -1483,6 +1533,7 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ndpi, ImageFormat.JPEG, rawdata[offset : offset + mpent["Size"]], + None, imgwidthpx, imgheightpx, [], @@ -1507,31 +1558,37 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): color, ndpi, imgwidthpx, imgheightpx, rotation, iccp = get_imgmetadata( imgdata, imgformat, default_dpi, colorspace, rawdata, rot ) - pngidat, palette = parse_png(rawdata) - # PIL does not provide the information about the original bits per - # sample. Thus, we retrieve that info manually by looking at byte 9 in - # the IHDR chunk. We know where to find that in the file because the - # IHDR chunk must be the first chunk - depth = rawdata[24] - if depth not in [1, 2, 4, 8, 16]: - raise ValueError("invalid bit depth: %d" % depth) - logger.debug("read_images() embeds a PNG") - cleanup() - return [ - ( - color, - ndpi, - imgformat, - pngidat, - imgwidthpx, - imgheightpx, - palette, - False, - depth, - rotation, - iccp, - ) - ] + if ( + color != Colorspace.RGBA + and color != Colorspace.LA + and "transparency" not in imgdata.info + ): + pngidat, palette = parse_png(rawdata) + # PIL does not provide the information about the original bits per + # sample. Thus, we retrieve that info manually by looking at byte 9 in + # the IHDR chunk. We know where to find that in the file because the + # IHDR chunk must be the first chunk + depth = rawdata[24] + if depth not in [1, 2, 4, 8, 16]: + raise ValueError("invalid bit depth: %d" % depth) + logger.debug("read_images() embeds a PNG") + cleanup() + return [ + ( + color, + ndpi, + imgformat, + pngidat, + None, + imgwidthpx, + imgheightpx, + palette, + False, + depth, + rotation, + iccp, + ) + ] # If our input is not JPEG or PNG, then we might have a format that # supports multiple frames (like TIFF or GIF), so we need a loop to @@ -1615,6 +1672,7 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ndpi, ImageFormat.CCITTGroup4, rawdata, + None, imgwidthpx, imgheightpx, [], @@ -1644,6 +1702,7 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ndpi, ImageFormat.CCITTGroup4, ccittdata, + None, imgwidthpx, imgheightpx, [], @@ -1662,7 +1721,9 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): color = Colorspace.L elif color in [ Colorspace.RGB, + Colorspace.RGBA, Colorspace.L, + Colorspace.LA, Colorspace.CMYK, Colorspace["CMYK;I"], Colorspace.P, @@ -1682,6 +1743,7 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ndpi, imgformat, imggz, + None, imgwidthpx, imgheightpx, [], @@ -1692,27 +1754,42 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): ) ) else: - # cheapo version to retrieve a PNG encoding of the payload is to - # just save it with PIL. In the future this could be replaced by - # dedicated function applying the Paeth PNG filter to the raw pixel - pngbuffer = BytesIO() - newimg.save(pngbuffer, format="png") - pngidat, palette = parse_png(pngbuffer.getvalue()) - # PIL does not provide the information about the original bits per - # sample. Thus, we retrieve that info manually by looking at byte 9 in - # the IHDR chunk. We know where to find that in the file because the - # IHDR chunk must be the first chunk - pngbuffer.seek(24) - depth = ord(pngbuffer.read(1)) - if depth not in [1, 2, 4, 8, 16]: - raise ValueError("invalid bit depth: %d" % depth) + if ( + color == Colorspace.RGBA + or color == Colorspace.LA + or "transparency" in newimg.info + ): + if color == Colorspace.RGBA: + newcolor = color + r, g, b, a = newimg.split() + newimg = Image.merge("RGB", (r, g, b)) + elif color == Colorspace.LA: + newcolor = color + l, a = newimg.split() + newimg = l + else: + newcolor = Colorspace.RGBA + r, g, b, a = newimg.convert(mode="RGBA").split() + newimg = Image.merge("RGB", (r, g, b)) + + smaskidat, _, _ = to_png_data(a) + logger.warning( + "Image contains an alpha channel which will be stored " + "as a separate soft mask (/SMask) image in PDF." + ) + else: + newcolor = color + smaskidat = None + + pngidat, palette, depth = to_png_data(newimg) logger.debug("read_images() encoded an image as PNG") result.append( ( - color, + newcolor, ndpi, ImageFormat.PNG, pngidat, + smaskidat, imgwidthpx, imgheightpx, palette, @@ -1726,6 +1803,23 @@ def read_images(rawdata, colorspace, first_frame_only=False, rot=None): cleanup() return result +def to_png_data(img): + # cheapo version to retrieve a PNG encoding of the payload is to + # just save it with PIL. In the future this could be replaced by + # dedicated function applying the Paeth PNG filter to the raw pixel + pngbuffer = BytesIO() + img.save(pngbuffer, format="png") + + pngidat, palette = parse_png(pngbuffer.getvalue()) + # PIL does not provide the information about the original bits per + # sample. Thus, we retrieve that info manually by looking at byte 9 in + # the IHDR chunk. We know where to find that in the file because the + # IHDR chunk must be the first chunk + pngbuffer.seek(24) + depth = ord(pngbuffer.read(1)) + if depth not in [1, 2, 4, 8, 16]: + raise ValueError("invalid bit depth: %d" % depth) + return pngidat, palette, depth # converts a length in pixels to a length in PDF units (1/72 of an inch) def px_to_pt(length, dpi): @@ -2118,6 +2212,7 @@ def convert(*images, **kwargs): ndpi, imgformat, imgdata, + smaskdata, imgwidthpx, imgheightpx, palette, @@ -2171,6 +2266,7 @@ def convert(*images, **kwargs): imgheightpx, imgformat, imgdata, + smaskdata, imgwidthpdf, imgheightpdf, imgxpdf, diff --git a/src/img2pdf_test.py b/src/img2pdf_test.py index bdb8c7d..3890b16 100755 --- a/src/img2pdf_test.py +++ b/src/img2pdf_test.py @@ -406,6 +406,23 @@ def compare_pdfimages_tiff(tmpdir, img, pdf): def compare_pdfimages_png(tmpdir, img, pdf, exact=True, icc=False): subprocess.check_call(["pdfimages", "-png", str(pdf), str(tmpdir / "images")]) + # images-001.png is the grayscale SMask image (the original alpha channel) + if os.path.isfile(tmpdir / "images-001.png"): + subprocess.check_call( + [ + "convert", + str(tmpdir / "images-000.png"), + str(tmpdir / "images-001.png"), + "-compose", + "copy-opacity", + "-composite", + str(tmpdir / "composite.png") + ] + ) + (tmpdir / "images-000.png").unlink() + (tmpdir / "images-001.png").unlink() + os.rename(tmpdir / "composite.png", tmpdir / "images-000.png") + if exact: if icc: raise Exception("not exact with icc") @@ -2287,7 +2304,15 @@ def tiff_float_img(tmp_path_factory, tmp_normal_png): 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)] + [ + "convert", + str(tmp_normal_png), + "-colorspace", + "cmyk", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2344,6 +2369,8 @@ def tiff_cmyk16_img(tmp_path_factory, tmp_normal_png): "16", "-colorspace", "cmyk", + "-compress", + "Zip", str(in_img), ] ) @@ -2394,7 +2421,15 @@ def tiff_cmyk16_img(tmp_path_factory, tmp_normal_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_normal_png), + "-compress", + "Zip", + 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 @@ -2442,7 +2477,15 @@ def tiff_rgb8_img(tmp_path_factory, tmp_normal_png): 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)] + [ + "convert", + str(tmp_normal16_png), + "-depth", + "12", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2495,7 +2538,15 @@ def tiff_rgb12_img(tmp_path_factory, tmp_normal16_png): 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)] + [ + "convert", + str(tmp_normal16_png), + "-depth", + "14", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2548,7 +2599,15 @@ def tiff_rgb14_img(tmp_path_factory, tmp_normal16_png): 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)] + [ + "convert", + str(tmp_normal16_png), + "-depth", + "16", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2597,7 +2656,16 @@ def tiff_rgb16_img(tmp_path_factory, tmp_normal16_png): 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)] + [ + "convert", + str(tmp_alpha_png), + "-depth", + "8", + "-strip", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2646,7 +2714,16 @@ def tiff_rgba8_img(tmp_path_factory, tmp_alpha_png): 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)] + [ + "convert", + str(tmp_alpha_png), + "-depth", + "16", + "-strip", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads(subprocess.check_output(["convert", str(in_img), "json:"])) assert len(identify) == 1 @@ -2694,7 +2771,17 @@ def tiff_rgba16_img(tmp_path_factory, tmp_alpha_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_gray1_png), + "-depth", + "1", + "-compress", + "Zip", + 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 @@ -2742,7 +2829,17 @@ def tiff_gray1_img(tmp_path_factory, tmp_gray1_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_gray2_png), + "-depth", + "2", + "-compress", + "Zip", + 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 @@ -2790,7 +2887,17 @@ def tiff_gray2_img(tmp_path_factory, tmp_gray2_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_gray4_png), + "-depth", + "4", + "-compress", + "Zip", + 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 @@ -2838,7 +2945,17 @@ def tiff_gray4_img(tmp_path_factory, tmp_gray4_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_gray8_png), + "-depth", + "8", + "-compress", + "Zip", + 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 @@ -2886,7 +3003,17 @@ def tiff_gray8_img(tmp_path_factory, tmp_gray8_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_gray16_png), + "-depth", + "16", + "-compress", + "Zip", + 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 @@ -2935,7 +3062,15 @@ def tiff_gray16_img(tmp_path_factory, tmp_gray16_png): 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", str(tmp_normal_png), str(tmp_inverse_png), "-strip", str(in_img)] + [ + "convert", + str(tmp_normal_png), + str(tmp_inverse_png), + "-strip", + "-compress", + "Zip", + str(in_img) + ] ) identify = json.loads( subprocess.check_output(["convert", str(in_img) + "[0]", "json:"]) @@ -3027,7 +3162,15 @@ def tiff_multipage_img(tmp_path_factory, tmp_normal_png, tmp_inverse_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_palette1_png), + "-compress", + "Zip", + 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 @@ -3076,7 +3219,15 @@ def tiff_palette1_img(tmp_path_factory, tmp_palette1_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_palette2_png), + "-compress", + "Zip", + 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 @@ -3125,7 +3276,15 @@ def tiff_palette2_img(tmp_path_factory, tmp_palette2_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_palette4_png), + "-compress", + "Zip", + 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 @@ -3174,7 +3333,15 @@ def tiff_palette4_img(tmp_path_factory, tmp_palette4_png): @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)]) + subprocess.check_call( + [ + "convert", + str(tmp_palette8_png), + "-compress", + "Zip", + 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 @@ -3234,6 +3401,8 @@ def tiff_ccitt_lsb_m2l_white_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=msb", "-define", "quantum:polarity=min-is-white", + "-compress", + "Group4", str(in_img), ] ) @@ -3313,6 +3482,8 @@ def tiff_ccitt_msb_m2l_white_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=msb", "-define", "quantum:polarity=min-is-white", + "-compress", + "Group4", str(in_img), ] ) @@ -3393,6 +3564,8 @@ def tiff_ccitt_msb_l2m_white_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=lsb", "-define", "quantum:polarity=min-is-white", + "-compress", + "Group4", str(in_img), ] ) @@ -3478,6 +3651,8 @@ def tiff_ccitt_lsb_m2l_black_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=msb", "-define", "quantum:polarity=min-is-black", + "-compress", + "Group4", str(in_img), ] ) @@ -3557,6 +3732,8 @@ def tiff_ccitt_nometa1_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=msb", "-define", "quantum:polarity=min-is-white", + "-compress", + "Group4", str(in_img), ] ) @@ -3645,6 +3822,8 @@ def tiff_ccitt_nometa2_img(tmp_path_factory, tmp_gray1_png): "tiff:fill-order=msb", "-define", "quantum:polarity=min-is-white", + "-compress", + "Group4", str(in_img), ] ) @@ -3904,6 +4083,86 @@ def png_rgb8_pdf(tmp_path_factory, png_rgb8_img, request): out_pdf.unlink() +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_rgba8_pdf(tmp_path_factory, png_rgba8_img, request): + out_pdf = tmp_path_factory.mktemp("png_rgba8_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(png_rgba8_img), + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + assert p.pages[0].Resources.XObject.Im0.SMask is not None + + assert p.pages[0].Resources.XObject.Im0.SMask.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.SMask.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.SMask.Height == 60 + assert p.pages[0].Resources.XObject.Im0.SMask.Width == 60 + yield out_pdf + out_pdf.unlink() + + +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def gif_transparent_pdf(tmp_path_factory, gif_transparent_img, request): + out_pdf = tmp_path_factory.mktemp("gif_transparent_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(gif_transparent_img), + ] + ) + with pikepdf.open(str(out_pdf)) as p: + assert ( + p.pages[0].Contents.read_bytes() + == b"q\n45.0000 0 0 45.0000 0.0000 0.0000 cm\n/Im0 Do\nQ" + ) + assert p.pages[0].Resources.XObject.Im0.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.ColorSpace == "/DeviceRGB" + assert p.pages[0].Resources.XObject.Im0.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Colors == 3 + assert p.pages[0].Resources.XObject.Im0.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.Height == 60 + assert p.pages[0].Resources.XObject.Im0.Width == 60 + assert p.pages[0].Resources.XObject.Im0.SMask is not None + + assert p.pages[0].Resources.XObject.Im0.SMask.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.SMask.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.SMask.Height == 60 + assert p.pages[0].Resources.XObject.Im0.SMask.Width == 60 + 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" @@ -4084,6 +4343,46 @@ def png_gray8_pdf(tmp_path_factory, tmp_gray8_png, request): out_pdf.unlink() +@pytest.fixture(scope="session", params=["internal", "pikepdf", "pdfrw"]) +def png_gray8a_pdf(tmp_path_factory, png_gray8a_img, request): + out_pdf = tmp_path_factory.mktemp("png_gray8a_pdf") / "out.pdf" + subprocess.check_call( + [ + "src/img2pdf.py", + "--producer=", + "--nodate", + "--engine=" + request.param, + "--output=" + str(out_pdf), + str(png_gray8a_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 + assert p.pages[0].Resources.XObject.Im0.SMask is not None + + assert p.pages[0].Resources.XObject.Im0.SMask.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.ColorSpace == "/DeviceGray" + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.BitsPerComponent == 8 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Colors == 1 + assert p.pages[0].Resources.XObject.Im0.SMask.DecodeParms.Predictor == 15 + assert p.pages[0].Resources.XObject.Im0.SMask.Filter == "/FlateDecode" + assert p.pages[0].Resources.XObject.Im0.SMask.Height == 60 + assert p.pages[0].Resources.XObject.Im0.SMask.Width == 60 + 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" @@ -5048,7 +5347,7 @@ def test_jpg_cmyk(tmp_path_factory, jpg_cmyk_img, jpg_cmyk_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.skipif( @@ -5063,7 +5362,7 @@ def test_jpg_2000(tmp_path_factory, jpg_2000_img, jpg_2000_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_rgb8(tmp_path_factory, png_rgb8_img, png_rgb8_pdf): @@ -5075,7 +5374,7 @@ def test_png_rgb8(tmp_path_factory, png_rgb8_img, png_rgb8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_rgb16(tmp_path_factory, png_rgb16_img, png_rgb16_pdf): @@ -5087,30 +5386,15 @@ def test_png_rgb16(tmp_path_factory, png_rgb16_img, png_rgb16_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) -@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), - str(png_rgba8_img), - ] - ).returncode - ) - out_pdf.unlink() - +def test_png_rgba8(tmp_path_factory, png_rgba8_img, png_rgba8_pdf): + tmpdir = tmp_path_factory.mktemp("png_rgba8") + compare_pdfimages_png(tmpdir, png_rgba8_img, png_rgba8_pdf) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5133,30 +5417,16 @@ def test_png_rgba16(tmp_path_factory, png_rgba16_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) -@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), - str(png_gray8a_img), - ] - ).returncode - ) - out_pdf.unlink() +def test_png_gray8a(tmp_path_factory, png_gray8a_img, png_gray8a_pdf): + tmpdir = tmp_path_factory.mktemp("png_gray8a") + compare_pdfimages_png(tmpdir, png_gray8a_img, png_gray8a_pdf) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5179,7 +5449,7 @@ def test_png_gray16a(tmp_path_factory, png_gray16a_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_interlaced(tmp_path_factory, png_interlaced_img, png_interlaced_pdf): @@ -5191,7 +5461,7 @@ def test_png_interlaced(tmp_path_factory, png_interlaced_img, png_interlaced_pdf @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_gray1(tmp_path_factory, png_gray1_img, png_gray1_pdf): @@ -5203,7 +5473,7 @@ def test_png_gray1(tmp_path_factory, png_gray1_img, png_gray1_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_gray2(tmp_path_factory, png_gray2_img, png_gray2_pdf): @@ -5215,7 +5485,7 @@ def test_png_gray2(tmp_path_factory, png_gray2_img, png_gray2_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_gray4(tmp_path_factory, png_gray4_img, png_gray4_pdf): @@ -5227,7 +5497,7 @@ def test_png_gray4(tmp_path_factory, png_gray4_img, png_gray4_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_gray8(tmp_path_factory, png_gray8_img, png_gray8_pdf): @@ -5239,7 +5509,7 @@ def test_png_gray8(tmp_path_factory, png_gray8_img, png_gray8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_gray16(tmp_path_factory, png_gray16_img, png_gray16_pdf): @@ -5255,7 +5525,7 @@ def test_png_gray16(tmp_path_factory, png_gray16_img, png_gray16_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_palette1(tmp_path_factory, png_palette1_img, png_palette1_pdf): @@ -5267,7 +5537,7 @@ def test_png_palette1(tmp_path_factory, png_palette1_img, png_palette1_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_palette2(tmp_path_factory, png_palette2_img, png_palette2_pdf): @@ -5279,7 +5549,7 @@ def test_png_palette2(tmp_path_factory, png_palette2_img, png_palette2_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_palette4(tmp_path_factory, png_palette4_img, png_palette4_pdf): @@ -5291,7 +5561,7 @@ def test_png_palette4(tmp_path_factory, png_palette4_img, png_palette4_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_png_palette8(tmp_path_factory, png_palette8_img, png_palette8_pdf): @@ -5315,30 +5585,16 @@ def test_png_icc(tmp_path_factory, png_icc_img, png_icc_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) -@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), - str(gif_transparent_img), - ] - ).returncode - ) - out_pdf.unlink() +def test_gif_transparent(tmp_path_factory, gif_transparent_img, gif_transparent_pdf): + tmpdir = tmp_path_factory.mktemp("gif_transparent") + compare_pdfimages_png(tmpdir, gif_transparent_img, gif_transparent_pdf) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_gif_palette1(tmp_path_factory, gif_palette1_img, gif_palette1_pdf): @@ -5350,7 +5606,7 @@ def test_gif_palette1(tmp_path_factory, gif_palette1_img, gif_palette1_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_gif_palette2(tmp_path_factory, gif_palette2_img, gif_palette2_pdf): @@ -5362,7 +5618,7 @@ def test_gif_palette2(tmp_path_factory, gif_palette2_img, gif_palette2_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_gif_palette4(tmp_path_factory, gif_palette4_img, gif_palette4_pdf): @@ -5374,7 +5630,7 @@ def test_gif_palette4(tmp_path_factory, gif_palette4_img, gif_palette4_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_gif_palette8(tmp_path_factory, gif_palette8_img, gif_palette8_pdf): @@ -5386,7 +5642,7 @@ def test_gif_palette8(tmp_path_factory, gif_palette8_img, gif_palette8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_gif_animation(tmp_path_factory, gif_animation_img, gif_animation_pdf): @@ -5433,7 +5689,7 @@ def test_tiff_float(tmp_path_factory, tiff_float_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_cmyk8(tmp_path_factory, tiff_cmyk8_img, tiff_cmyk8_pdf): @@ -5447,7 +5703,7 @@ def test_tiff_cmyk8(tmp_path_factory, tiff_cmyk8_img, tiff_cmyk8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5471,7 +5727,7 @@ def test_tiff_cmyk16(tmp_path_factory, tiff_cmyk16_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_rgb8(tmp_path_factory, tiff_rgb8_img, tiff_rgb8_pdf): @@ -5483,7 +5739,7 @@ def test_tiff_rgb8(tmp_path_factory, tiff_rgb8_img, tiff_rgb8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5507,7 +5763,7 @@ def test_tiff_rgb12(tmp_path_factory, tiff_rgb12_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5531,7 +5787,7 @@ def test_tiff_rgb14(tmp_path_factory, tiff_rgb14_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5555,7 +5811,7 @@ def test_tiff_rgb16(tmp_path_factory, tiff_rgb16_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5578,7 +5834,7 @@ def test_tiff_rgba8(tmp_path_factory, tiff_rgba8_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5601,7 +5857,7 @@ def test_tiff_rgba16(tmp_path_factory, tiff_rgba16_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_gray1(tmp_path_factory, tiff_gray1_img, tiff_gray1_pdf): @@ -5613,7 +5869,7 @@ def test_tiff_gray1(tmp_path_factory, tiff_gray1_img, tiff_gray1_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_gray2(tmp_path_factory, tiff_gray2_img, tiff_gray2_pdf): @@ -5625,7 +5881,7 @@ def test_tiff_gray2(tmp_path_factory, tiff_gray2_img, tiff_gray2_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_gray4(tmp_path_factory, tiff_gray4_img, tiff_gray4_pdf): @@ -5637,7 +5893,7 @@ def test_tiff_gray4(tmp_path_factory, tiff_gray4_img, tiff_gray4_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_gray8(tmp_path_factory, tiff_gray8_img, tiff_gray8_pdf): @@ -5649,7 +5905,7 @@ def test_tiff_gray8(tmp_path_factory, tiff_gray8_img, tiff_gray8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) @pytest.mark.parametrize("engine", ["internal", "pikepdf", "pdfrw"]) @@ -5672,7 +5928,7 @@ def test_tiff_gray16(tmp_path_factory, tiff_gray16_img, engine): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_multipage(tmp_path_factory, tiff_multipage_img, tiff_multipage_pdf): @@ -5702,7 +5958,7 @@ def test_tiff_multipage(tmp_path_factory, tiff_multipage_img, tiff_multipage_pdf reason="requires imagemagick with support for keeping the palette depth", ) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_palette1(tmp_path_factory, tiff_palette1_img, tiff_palette1_pdf): @@ -5718,7 +5974,7 @@ def test_tiff_palette1(tmp_path_factory, tiff_palette1_img, tiff_palette1_pdf): reason="requires imagemagick with support for keeping the palette depth", ) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_palette2(tmp_path_factory, tiff_palette2_img, tiff_palette2_pdf): @@ -5734,7 +5990,7 @@ def test_tiff_palette2(tmp_path_factory, tiff_palette2_img, tiff_palette2_pdf): reason="requires imagemagick with support for keeping the palette depth", ) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_palette4(tmp_path_factory, tiff_palette4_img, tiff_palette4_pdf): @@ -5746,7 +6002,7 @@ def test_tiff_palette4(tmp_path_factory, tiff_palette4_img, tiff_palette4_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_palette8(tmp_path_factory, tiff_palette8_img, tiff_palette8_pdf): @@ -5758,7 +6014,7 @@ def test_tiff_palette8(tmp_path_factory, tiff_palette8_img, tiff_palette8_pdf): @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_lsb_m2l_white( @@ -5779,7 +6035,7 @@ def test_tiff_ccitt_lsb_m2l_white( @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_msb_m2l_white( @@ -5800,7 +6056,7 @@ def test_tiff_ccitt_msb_m2l_white( @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_msb_l2m_white( @@ -5825,7 +6081,7 @@ def test_tiff_ccitt_msb_l2m_white( reason="requires imagemagick with support for min-is-black", ) @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_lsb_m2l_black( @@ -5846,7 +6102,7 @@ def test_tiff_ccitt_lsb_m2l_black( @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_nometa1( @@ -5862,7 +6118,7 @@ def test_tiff_ccitt_nometa1( @pytest.mark.skipif( - sys.platform in ["darwin", "win32"], + sys.platform in ["win32"], reason="test utilities not available on Windows and MacOS", ) def test_tiff_ccitt_nometa2(