Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion drawBot/context/imageContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def _tiffCompressionConverter(value):
return t.get(value.lower(), AppKit.NSTIFFCompressionNone)


def _colorSpaceConverter(value):
if value == "CMYK":
return AppKit.NSDeviceCMYKColorSpace
return AppKit.NSCalibratedRGBColorSpace


_nsImageOptions = {
# DrawBot Key: (
# NSImage property key,
Expand Down Expand Up @@ -118,6 +124,7 @@ class ImageContext(PDFContext):
"A Boolean value that specifies whether subpixel quantization of glyphs is allowed. Default is True.",
),
("multipage", "Output a numbered image for each page or frame in the document."),
("colorSpace", "Set color space (RGB, CMYK). Default is RGB."),
]

ensureEvenPixelDimensions = False
Expand All @@ -138,6 +145,10 @@ def _writeDataToFile(self, data, path, options):
imageResolution = options.get("imageResolution", 72.0)
antiAliasing = options.get("antiAliasing", True)
fontSubpixelQuantization = options.get("fontSubpixelQuantization", True)

colorSpace = options.get("colorSpace", "RGB")
colorSpaceName = _colorSpaceConverter(colorSpace)

properties = {}
for key, value in options.items():
if key in _nsImageOptions:
Expand All @@ -154,7 +165,12 @@ def _writeDataToFile(self, data, path, options):
antiAliasing=antiAliasing,
fontSubpixelQuantization=fontSubpixelQuantization,
imageResolution=imageResolution,
colorSpaceName=colorSpaceName,
)

if "imageColorSyncProfileData" in options:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this instead be handled by the _makeBitmapImageRep() helper function, with an additional keyword argument?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is also handled by line 174: imageData = imageRep.representationUsingType_properties_(self._saveImageFileTypes[ext], properties).

I added this because it initially didn't work as expected, but it now does.

imageRep.setProperty_withValue_(AppKit.NSImageColorSyncProfileData, options["imageColorSyncProfileData"]) # doing this with representationUsingType_properties_ does not work

if self.ensureEvenPixelDimensions:
if imageRep.pixelsWide() % 2 or imageRep.pixelsHigh() % 2:
msg = f"Exporting to {', '.join(self.fileExtensions)} doesn't support odd pixel dimensions for width and height."
Expand Down Expand Up @@ -189,13 +205,17 @@ def _makeBitmapImageRep(
elif nsImage is not None:
width, height = nsImage.size()

hasAlpha = True
if colorSpaceName == AppKit.NSDeviceCMYKColorSpace:
hasAlpha = False # Quartz doesn’t support alpha for CMYK bitmaps.

rep = AppKit.NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
None, # planes
int(width * scaleFactor), # pixelsWide
int(height * scaleFactor), # pixelsHigh
8, # bitsPerSample
4, # samplesPerPixel
True, # hasAlpha
hasAlpha, # hasAlpha
False, # isPlanar
colorSpaceName, # colorSpaceName
0, # bytesPerRow
Expand Down
51 changes: 51 additions & 0 deletions tests/testExport.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import glob
import io
import os
import random
import sys
import unittest

import AppKit # type: ignore
import PIL
from PIL import ImageCms
from testSupport import (
DrawBotBaseTest,
StdOutCollector,
Expand Down Expand Up @@ -444,6 +446,55 @@ def test_formattedStringURL_svg(self):
readData(path), readData(expectedPath), "Files %r and %s are not the same" % (path, expectedPath)
)

def test_export_color_space_cmyk(self):
drawBot.newDrawing()
drawBot.size(1000, 1000)
drawBot.cmykFill(1, 0, 0, 0)
drawBot.rect(0, 0, 250, 1000)
drawBot.cmykFill(0, 1, 0, 0)
drawBot.rect(250, 0, 250, 1000)
drawBot.cmykFill(0, 0, 1, 0)
drawBot.rect(500, 0, 250, 1000)
drawBot.cmykFill(0, 0, 0, 1)
drawBot.rect(750, 0, 250, 1000)

rgba_path = os.path.join(tempTestDataDir, "cmyk_export_default.tiff")
cmyk_path = os.path.join(tempTestDataDir, "cmyk_export_with_color_space.tiff")
cmyk_with_profile_path = os.path.join(tempTestDataDir, "cmyk_export_with_color_space_and_profile.tiff")

drawBot.saveImage(rgba_path)
drawBot.saveImage(cmyk_path, colorSpace="CMYK")

icc_data = AppKit.NSData.dataWithContentsOfFile_("/System/Library/ColorSync/Profiles/Generic CMYK Profile.icc")
drawBot.saveImage(cmyk_with_profile_path, colorSpace="CMYK", imageColorSyncProfileData=icc_data)

drawBot.endDrawing()

rgba_image = PIL.Image.open(rgba_path)
self.assertEqual(rgba_image.mode, "RGBA")

rgba_image_profile = ImageCms.ImageCmsProfile(
io.BytesIO(rgba_image.info.get("icc_profile"))
)
self.assertEqual(
rgba_image_profile.profile.profile_description,
"Generic RGB Profile"
)

cmyk_image = PIL.Image.open(cmyk_path)
self.assertEqual(cmyk_image.mode, "CMYK")
self.assertIsNone(cmyk_image.info.get("icc_profile"))

cmyk_with_profile_image = PIL.Image.open(cmyk_with_profile_path)
self.assertEqual(cmyk_with_profile_image.mode, "CMYK")
cmyk_with_profile_image_profile = ImageCms.ImageCmsProfile(
io.BytesIO(cmyk_with_profile_image.info.get("icc_profile"))
)
self.assertEqual(
cmyk_with_profile_image_profile.profile.profile_description,
"Generic CMYK Profile"
)


if __name__ == "__main__":
import doctest
Expand Down