Skip to content

Commit

Permalink
Add cmyk support (#756)
Browse files Browse the repository at this point in the history
  • Loading branch information
devdev29 committed Apr 26, 2023
1 parent c55f1e3 commit e79eeff
Show file tree
Hide file tree
Showing 8 changed files with 31 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',

## [2.7.4] - Not released yet
### Added
- [`FPDF.image()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.image): CMYK images can now be inserted directly by passing them into the image method. Contributed by @devdev29
- documentation on how to embed `graphs` and `charts` generated using `Pygal` lib: [documentation section](https://pyfpdf.github.io/fpdf2/Maths.html#using-pygal) - thanks to @ssavi-ict
- documentation on how to use `fpdf2` with [FastAPI](https://fastapi.tiangolo.com/): <https://pyfpdf.github.io/fpdf2/UsageInWebAPI.html#FastAPI> - thanks to @KamarulAdha
- [`FPDF.write_html()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): `<table>` elements can now be aligned left or right on the page using `align=`
Expand Down
15 changes: 13 additions & 2 deletions fpdf/image_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
raise EnvironmentError("Pillow not available - fpdf2 cannot insert images")

is_pil_img = True
jpeg_inverted = False # flag to check whether a cmyk image is jpeg or not, if set to True the decode array is inverted in output.py
img_raw_data = None
if not img or isinstance(img, (Path, str)):
img_raw_data = load_image(filename)
Expand Down Expand Up @@ -146,7 +147,7 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
if img.mode in ("P", "PA") and image_filter != "FlateDecode":
img = img.convert("RGBA")

if img.mode not in ("1", "L", "LA", "RGB", "RGBA", "P", "PA"):
if img.mode not in ("1", "L", "LA", "RGB", "RGBA", "P", "PA", "CMYK"):
img = img.convert("RGBA")
img_altered = True

Expand All @@ -161,7 +162,11 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
if img_raw_data is not None and not img_altered:
# if we can use the original image bytes directly we do (JPEG and group4 TIFF only):
if img.format == "JPEG" and image_filter == "DCTDecode":
dpn, bpc, colspace = 3, 8, "DeviceRGB"
if img.mode in ("RGB", "RGBA"):
dpn, bpc, colspace = 3, 8, "DeviceRGB"
elif img.mode == "CMYK":
dpn, bpc, colspace = 4, 8, "DeviceCMYK"
jpeg_inverted = True
if img.mode == "L":
dpn, bpc, colspace = 1, 8, "DeviceGray"
img_raw_data.seek(0)
Expand All @@ -174,6 +179,7 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
"dpn": dpn,
"bpc": bpc,
"f": image_filter,
"inverted": jpeg_inverted,
"dp": f"/Predictor 15 /Colors {dpn} /Columns {w}",
}
# We can directly copy the data out of a CCITT Group 4 encoded TIFF, if it
Expand Down Expand Up @@ -218,6 +224,7 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
"cs": colspace,
"bpc": bpc,
"f": image_filter,
"inverted": jpeg_inverted,
"dp": f"/BlackIs1 {str(not inverted).lower()} /Columns {w} /K -1 /Rows {h}",
}

Expand Down Expand Up @@ -263,6 +270,9 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
"JPXDecode",
):
info["smask"] = _to_data(img, image_filter, select_slice=alpha_channel)
elif img.mode == "CMYK":
dpn, bpc, colspace = 4, 8, "DeviceCMYK"
info["data"] = _to_data(img, image_filter)
elif img.mode == "RGB":
dpn, bpc, colspace = 3, 8, "DeviceRGB"
info["data"] = _to_data(img, image_filter)
Expand Down Expand Up @@ -293,6 +303,7 @@ def get_img_info(filename, img=None, image_filter="AUTO", dims=None):
"bpc": bpc,
"dpn": dpn,
"f": image_filter,
"inverted": jpeg_inverted,
"dp": dp,
}
)
Expand Down
6 changes: 2 additions & 4 deletions fpdf/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,10 +766,8 @@ def _add_image(self, info):
iccp_pdf_i = self._ensure_iccp(info)
color_space = PDFArray(["/ICCBased", str(iccp_pdf_i), str("0"), "R"])
elif color_space == "DeviceCMYK":
decode = "[1 0 1 0 1 0 1 0]"
raise NotImplementedError(
"fpdf2 does not support DeviceCMYK ColorSpace yet - cf. issue #711"
)
if info["inverted"] is True:
decode = "[1 0 1 0 1 0 1 0]"

decode_parms = f"<<{info['dp']} /BitsPerComponent {info['bpc']}>>"
img_obj = PDFXObject(
Expand Down
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
15 changes: 15 additions & 0 deletions test/image/image_types/test_insert_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ def test_insert_jpg_flatedecode(tmp_path):
assert_pdf_equal(pdf, HERE / "image_types_insert_jpg_flatedecode.pdf", tmp_path)


def test_insert_jpg_cmyk(tmp_path):
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
pdf.image(HERE / "insert_images_insert_jpg_cmyk.jpg", x=15, y=15)
assert_pdf_equal(pdf, HERE / "images_types_insert_jpg_cmyk.pdf", tmp_path)


def test_insert_png(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
Expand Down Expand Up @@ -168,6 +176,13 @@ def test_insert_g4_tiff(tmp_path):
assert_pdf_equal(pdf, HERE / "image_types_insert_tiff.pdf", tmp_path)


def test_insert_tiff_cmyk(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image(HERE / "insert_images_insert_tiff_cmyk.tiff", x=15, y=15)
assert_pdf_equal(pdf, HERE / "image_types_insert_tiff_cmyk.pdf", tmp_path)


def test_insert_pillow(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
Expand Down

0 comments on commit e79eeff

Please sign in to comment.