Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/ImageCms.py: 34%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

290 statements  

1# The Python Imaging Library. 

2# $Id$ 

3 

4# Optional color management support, based on Kevin Cazabon's PyCMS 

5# library. 

6 

7# Originally released under LGPL. Graciously donated to PIL in 

8# March 2009, for distribution under the standard PIL license 

9 

10# History: 

11 

12# 2009-03-08 fl Added to PIL. 

13 

14# Copyright (C) 2002-2003 Kevin Cazabon 

15# Copyright (c) 2009 by Fredrik Lundh 

16# Copyright (c) 2013 by Eric Soroos 

17 

18# See the README file for information on usage and redistribution. See 

19# below for the original description. 

20from __future__ import annotations 

21 

22import operator 

23import sys 

24from enum import IntEnum, IntFlag 

25from functools import reduce 

26from typing import Any, Literal, SupportsFloat, SupportsInt, Union 

27 

28from . import Image 

29from ._deprecate import deprecate 

30from ._typing import SupportsRead 

31 

32try: 

33 from . import _imagingcms as core 

34 

35 _CmsProfileCompatible = Union[ 

36 str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile" 

37 ] 

38except ImportError as ex: 

39 # Allow error import for doc purposes, but error out when accessing 

40 # anything in core. 

41 from ._util import DeferredError 

42 

43 core = DeferredError.new(ex) 

44 

45_DESCRIPTION = """ 

46pyCMS 

47 

48 a Python / PIL interface to the littleCMS ICC Color Management System 

49 Copyright (C) 2002-2003 Kevin Cazabon 

50 kevin@cazabon.com 

51 https://www.cazabon.com 

52 

53 pyCMS home page: https://www.cazabon.com/pyCMS 

54 littleCMS home page: https://www.littlecms.com 

55 (littleCMS is Copyright (C) 1998-2001 Marti Maria) 

56 

57 Originally released under LGPL. Graciously donated to PIL in 

58 March 2009, for distribution under the standard PIL license 

59 

60 The pyCMS.py module provides a "clean" interface between Python/PIL and 

61 pyCMSdll, taking care of some of the more complex handling of the direct 

62 pyCMSdll functions, as well as error-checking and making sure that all 

63 relevant data is kept together. 

64 

65 While it is possible to call pyCMSdll functions directly, it's not highly 

66 recommended. 

67 

68 Version History: 

69 

70 1.0.0 pil Oct 2013 Port to LCMS 2. 

71 

72 0.1.0 pil mod March 10, 2009 

73 

74 Renamed display profile to proof profile. The proof 

75 profile is the profile of the device that is being 

76 simulated, not the profile of the device which is 

77 actually used to display/print the final simulation 

78 (that'd be the output profile) - also see LCMSAPI.txt 

79 input colorspace -> using 'renderingIntent' -> proof 

80 colorspace -> using 'proofRenderingIntent' -> output 

81 colorspace 

82 

83 Added LCMS FLAGS support. 

84 Added FLAGS["SOFTPROOFING"] as default flag for 

85 buildProofTransform (otherwise the proof profile/intent 

86 would be ignored). 

87 

88 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms 

89 

90 0.0.2 alpha Jan 6, 2002 

91 

92 Added try/except statements around type() checks of 

93 potential CObjects... Python won't let you use type() 

94 on them, and raises a TypeError (stupid, if you ask 

95 me!) 

96 

97 Added buildProofTransformFromOpenProfiles() function. 

98 Additional fixes in DLL, see DLL code for details. 

99 

100 0.0.1 alpha first public release, Dec. 26, 2002 

101 

102 Known to-do list with current version (of Python interface, not pyCMSdll): 

103 

104 none 

105 

106""" 

107 

108_VERSION = "1.0.0 pil" 

109 

110 

111# --------------------------------------------------------------------. 

112 

113 

114# 

115# intent/direction values 

116 

117 

118class Intent(IntEnum): 

119 PERCEPTUAL = 0 

120 RELATIVE_COLORIMETRIC = 1 

121 SATURATION = 2 

122 ABSOLUTE_COLORIMETRIC = 3 

123 

124 

125class Direction(IntEnum): 

126 INPUT = 0 

127 OUTPUT = 1 

128 PROOF = 2 

129 

130 

131# 

132# flags 

133 

134 

135class Flags(IntFlag): 

136 """Flags and documentation are taken from ``lcms2.h``.""" 

137 

138 NONE = 0 

139 NOCACHE = 0x0040 

140 """Inhibit 1-pixel cache""" 

141 NOOPTIMIZE = 0x0100 

142 """Inhibit optimizations""" 

143 NULLTRANSFORM = 0x0200 

144 """Don't transform anyway""" 

145 GAMUTCHECK = 0x1000 

146 """Out of Gamut alarm""" 

147 SOFTPROOFING = 0x4000 

148 """Do softproofing""" 

149 BLACKPOINTCOMPENSATION = 0x2000 

150 NOWHITEONWHITEFIXUP = 0x0004 

151 """Don't fix scum dot""" 

152 HIGHRESPRECALC = 0x0400 

153 """Use more memory to give better accuracy""" 

154 LOWRESPRECALC = 0x0800 

155 """Use less memory to minimize resources""" 

156 # this should be 8BITS_DEVICELINK, but that is not a valid name in Python: 

157 USE_8BITS_DEVICELINK = 0x0008 

158 """Create 8 bits devicelinks""" 

159 GUESSDEVICECLASS = 0x0020 

160 """Guess device class (for ``transform2devicelink``)""" 

161 KEEP_SEQUENCE = 0x0080 

162 """Keep profile sequence for devicelink creation""" 

163 FORCE_CLUT = 0x0002 

164 """Force CLUT optimization""" 

165 CLUT_POST_LINEARIZATION = 0x0001 

166 """create postlinearization tables if possible""" 

167 CLUT_PRE_LINEARIZATION = 0x0010 

168 """create prelinearization tables if possible""" 

169 NONEGATIVES = 0x8000 

170 """Prevent negative numbers in floating point transforms""" 

171 COPY_ALPHA = 0x04000000 

172 """Alpha channels are copied on ``cmsDoTransform()``""" 

173 NODEFAULTRESOURCEDEF = 0x01000000 

174 

175 _GRIDPOINTS_1 = 1 << 16 

176 _GRIDPOINTS_2 = 2 << 16 

177 _GRIDPOINTS_4 = 4 << 16 

178 _GRIDPOINTS_8 = 8 << 16 

179 _GRIDPOINTS_16 = 16 << 16 

180 _GRIDPOINTS_32 = 32 << 16 

181 _GRIDPOINTS_64 = 64 << 16 

182 _GRIDPOINTS_128 = 128 << 16 

183 

184 @staticmethod 

185 def GRIDPOINTS(n: int) -> Flags: 

186 """ 

187 Fine-tune control over number of gridpoints 

188 

189 :param n: :py:class:`int` in range ``0 <= n <= 255`` 

190 """ 

191 return Flags.NONE | ((n & 0xFF) << 16) 

192 

193 

194_MAX_FLAG = reduce(operator.or_, Flags) 

195 

196 

197_FLAGS = { 

198 "MATRIXINPUT": 1, 

199 "MATRIXOUTPUT": 2, 

200 "MATRIXONLY": (1 | 2), 

201 "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot 

202 # Don't create prelinearization tables on precalculated transforms 

203 # (internal use): 

204 "NOPRELINEARIZATION": 16, 

205 "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) 

206 "NOTCACHE": 64, # Inhibit 1-pixel cache 

207 "NOTPRECALC": 256, 

208 "NULLTRANSFORM": 512, # Don't transform anyway 

209 "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy 

210 "LOWRESPRECALC": 2048, # Use less memory to minimize resources 

211 "WHITEBLACKCOMPENSATION": 8192, 

212 "BLACKPOINTCOMPENSATION": 8192, 

213 "GAMUTCHECK": 4096, # Out of Gamut alarm 

214 "SOFTPROOFING": 16384, # Do softproofing 

215 "PRESERVEBLACK": 32768, # Black preservation 

216 "NODEFAULTRESOURCEDEF": 16777216, # CRD special 

217 "GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints 

218} 

219 

220 

221# --------------------------------------------------------------------. 

222# Experimental PIL-level API 

223# --------------------------------------------------------------------. 

224 

225## 

226# Profile. 

227 

228 

229class ImageCmsProfile: 

230 def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None: 

231 """ 

232 :param profile: Either a string representing a filename, 

233 a file like object containing a profile or a 

234 low-level profile object 

235 

236 """ 

237 self.filename: str | None = None 

238 

239 if isinstance(profile, str): 

240 if sys.platform == "win32": 

241 profile_bytes_path = profile.encode() 

242 try: 

243 profile_bytes_path.decode("ascii") 

244 except UnicodeDecodeError: 

245 with open(profile, "rb") as f: 

246 self.profile = core.profile_frombytes(f.read()) 

247 return 

248 self.filename = profile 

249 self.profile = core.profile_open(profile) 

250 elif hasattr(profile, "read"): 

251 self.profile = core.profile_frombytes(profile.read()) 

252 elif isinstance(profile, core.CmsProfile): 

253 self.profile = profile 

254 else: 

255 msg = "Invalid type for Profile" # type: ignore[unreachable] 

256 raise TypeError(msg) 

257 

258 def __getattr__(self, name: str) -> Any: 

259 if name in ("product_name", "product_info"): 

260 deprecate(f"ImageCms.ImageCmsProfile.{name}", 13) 

261 return None 

262 msg = f"'{self.__class__.__name__}' object has no attribute '{name}'" 

263 raise AttributeError(msg) 

264 

265 def tobytes(self) -> bytes: 

266 """ 

267 Returns the profile in a format suitable for embedding in 

268 saved images. 

269 

270 :returns: a bytes object containing the ICC profile. 

271 """ 

272 

273 return core.profile_tobytes(self.profile) 

274 

275 

276class ImageCmsTransform(Image.ImagePointHandler): 

277 """ 

278 Transform. This can be used with the procedural API, or with the standard 

279 :py:func:`~PIL.Image.Image.point` method. 

280 

281 Will return the output profile in the ``output.info['icc_profile']``. 

282 """ 

283 

284 def __init__( 

285 self, 

286 input: ImageCmsProfile, 

287 output: ImageCmsProfile, 

288 input_mode: str, 

289 output_mode: str, 

290 intent: Intent = Intent.PERCEPTUAL, 

291 proof: ImageCmsProfile | None = None, 

292 proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC, 

293 flags: Flags = Flags.NONE, 

294 ): 

295 if proof is None: 

296 self.transform = core.buildTransform( 

297 input.profile, output.profile, input_mode, output_mode, intent, flags 

298 ) 

299 else: 

300 self.transform = core.buildProofTransform( 

301 input.profile, 

302 output.profile, 

303 proof.profile, 

304 input_mode, 

305 output_mode, 

306 intent, 

307 proof_intent, 

308 flags, 

309 ) 

310 # Note: inputMode and outputMode are for pyCMS compatibility only 

311 self.input_mode = self.inputMode = input_mode 

312 self.output_mode = self.outputMode = output_mode 

313 

314 self.output_profile = output 

315 

316 def point(self, im: Image.Image) -> Image.Image: 

317 return self.apply(im) 

318 

319 def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image: 

320 if imOut is None: 

321 imOut = Image.new(self.output_mode, im.size, None) 

322 self.transform.apply(im.getim(), imOut.getim()) 

323 imOut.info["icc_profile"] = self.output_profile.tobytes() 

324 return imOut 

325 

326 def apply_in_place(self, im: Image.Image) -> Image.Image: 

327 if im.mode != self.output_mode: 

328 msg = "mode mismatch" 

329 raise ValueError(msg) # wrong output mode 

330 self.transform.apply(im.getim(), im.getim()) 

331 im.info["icc_profile"] = self.output_profile.tobytes() 

332 return im 

333 

334 

335def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None: 

336 """ 

337 (experimental) Fetches the profile for the current display device. 

338 

339 :returns: ``None`` if the profile is not known. 

340 """ 

341 

342 if sys.platform != "win32": 

343 return None 

344 

345 from . import ImageWin # type: ignore[unused-ignore, unreachable] 

346 

347 if isinstance(handle, ImageWin.HDC): 

348 profile = core.get_display_profile_win32(int(handle), 1) 

349 else: 

350 profile = core.get_display_profile_win32(int(handle or 0)) 

351 if profile is None: 

352 return None 

353 return ImageCmsProfile(profile) 

354 

355 

356# --------------------------------------------------------------------. 

357# pyCMS compatible layer 

358# --------------------------------------------------------------------. 

359 

360 

361class PyCMSError(Exception): 

362 """(pyCMS) Exception class. 

363 This is used for all errors in the pyCMS API.""" 

364 

365 pass 

366 

367 

368def profileToProfile( 

369 im: Image.Image, 

370 inputProfile: _CmsProfileCompatible, 

371 outputProfile: _CmsProfileCompatible, 

372 renderingIntent: Intent = Intent.PERCEPTUAL, 

373 outputMode: str | None = None, 

374 inPlace: bool = False, 

375 flags: Flags = Flags.NONE, 

376) -> Image.Image | None: 

377 """ 

378 (pyCMS) Applies an ICC transformation to a given image, mapping from 

379 ``inputProfile`` to ``outputProfile``. 

380 

381 If the input or output profiles specified are not valid filenames, a 

382 :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and 

383 ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised. 

384 If an error occurs during application of the profiles, 

385 a :exc:`PyCMSError` will be raised. 

386 If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS), 

387 a :exc:`PyCMSError` will be raised. 

388 

389 This function applies an ICC transformation to im from ``inputProfile``'s 

390 color space to ``outputProfile``'s color space using the specified rendering 

391 intent to decide how to handle out-of-gamut colors. 

392 

393 ``outputMode`` can be used to specify that a color mode conversion is to 

394 be done using these profiles, but the specified profiles must be able 

395 to handle that mode. I.e., if converting im from RGB to CMYK using 

396 profiles, the input profile must handle RGB data, and the output 

397 profile must handle CMYK data. 

398 

399 :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...) 

400 or Image.open(...), etc.) 

401 :param inputProfile: String, as a valid filename path to the ICC input 

402 profile you wish to use for this image, or a profile object 

403 :param outputProfile: String, as a valid filename path to the ICC output 

404 profile you wish to use for this image, or a profile object 

405 :param renderingIntent: Integer (0-3) specifying the rendering intent you 

406 wish to use for the transform 

407 

408 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

409 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

410 ImageCms.Intent.SATURATION = 2 

411 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

412 

413 see the pyCMS documentation for details on rendering intents and what 

414 they do. 

415 :param outputMode: A valid PIL mode for the output image (i.e. "RGB", 

416 "CMYK", etc.). Note: if rendering the image "inPlace", outputMode 

417 MUST be the same mode as the input, or omitted completely. If 

418 omitted, the outputMode will be the same as the mode of the input 

419 image (im.mode) 

420 :param inPlace: Boolean. If ``True``, the original image is modified in-place, 

421 and ``None`` is returned. If ``False`` (default), a new 

422 :py:class:`~PIL.Image.Image` object is returned with the transform applied. 

423 :param flags: Integer (0-...) specifying additional flags 

424 :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on 

425 the value of ``inPlace`` 

426 :exception PyCMSError: 

427 """ 

428 

429 if outputMode is None: 

430 outputMode = im.mode 

431 

432 if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): 

433 msg = "renderingIntent must be an integer between 0 and 3" 

434 raise PyCMSError(msg) 

435 

436 if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): 

437 msg = f"flags must be an integer between 0 and {_MAX_FLAG}" 

438 raise PyCMSError(msg) 

439 

440 try: 

441 if not isinstance(inputProfile, ImageCmsProfile): 

442 inputProfile = ImageCmsProfile(inputProfile) 

443 if not isinstance(outputProfile, ImageCmsProfile): 

444 outputProfile = ImageCmsProfile(outputProfile) 

445 transform = ImageCmsTransform( 

446 inputProfile, 

447 outputProfile, 

448 im.mode, 

449 outputMode, 

450 renderingIntent, 

451 flags=flags, 

452 ) 

453 if inPlace: 

454 transform.apply_in_place(im) 

455 imOut = None 

456 else: 

457 imOut = transform.apply(im) 

458 except (OSError, TypeError, ValueError) as v: 

459 raise PyCMSError(v) from v 

460 

461 return imOut 

462 

463 

464def getOpenProfile( 

465 profileFilename: str | SupportsRead[bytes] | core.CmsProfile, 

466) -> ImageCmsProfile: 

467 """ 

468 (pyCMS) Opens an ICC profile file. 

469 

470 The PyCMSProfile object can be passed back into pyCMS for use in creating 

471 transforms and such (as in ImageCms.buildTransformFromOpenProfiles()). 

472 

473 If ``profileFilename`` is not a valid filename for an ICC profile, 

474 a :exc:`PyCMSError` will be raised. 

475 

476 :param profileFilename: String, as a valid filename path to the ICC profile 

477 you wish to open, or a file-like object. 

478 :returns: A CmsProfile class object. 

479 :exception PyCMSError: 

480 """ 

481 

482 try: 

483 return ImageCmsProfile(profileFilename) 

484 except (OSError, TypeError, ValueError) as v: 

485 raise PyCMSError(v) from v 

486 

487 

488def buildTransform( 

489 inputProfile: _CmsProfileCompatible, 

490 outputProfile: _CmsProfileCompatible, 

491 inMode: str, 

492 outMode: str, 

493 renderingIntent: Intent = Intent.PERCEPTUAL, 

494 flags: Flags = Flags.NONE, 

495) -> ImageCmsTransform: 

496 """ 

497 (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the 

498 ``outputProfile``. Use applyTransform to apply the transform to a given 

499 image. 

500 

501 If the input or output profiles specified are not valid filenames, a 

502 :exc:`PyCMSError` will be raised. If an error occurs during creation 

503 of the transform, a :exc:`PyCMSError` will be raised. 

504 

505 If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` 

506 (or by pyCMS), a :exc:`PyCMSError` will be raised. 

507 

508 This function builds and returns an ICC transform from the ``inputProfile`` 

509 to the ``outputProfile`` using the ``renderingIntent`` to determine what to do 

510 with out-of-gamut colors. It will ONLY work for converting images that 

511 are in ``inMode`` to images that are in ``outMode`` color format (PIL mode, 

512 i.e. "RGB", "RGBA", "CMYK", etc.). 

513 

514 Building the transform is a fair part of the overhead in 

515 ImageCms.profileToProfile(), so if you're planning on converting multiple 

516 images using the same input/output settings, this can save you time. 

517 Once you have a transform object, it can be used with 

518 ImageCms.applyProfile() to convert images without the need to re-compute 

519 the lookup table for the transform. 

520 

521 The reason pyCMS returns a class object rather than a handle directly 

522 to the transform is that it needs to keep track of the PIL input/output 

523 modes that the transform is meant for. These attributes are stored in 

524 the ``inMode`` and ``outMode`` attributes of the object (which can be 

525 manually overridden if you really want to, but I don't know of any 

526 time that would be of use, or would even work). 

527 

528 :param inputProfile: String, as a valid filename path to the ICC input 

529 profile you wish to use for this transform, or a profile object 

530 :param outputProfile: String, as a valid filename path to the ICC output 

531 profile you wish to use for this transform, or a profile object 

532 :param inMode: String, as a valid PIL mode that the appropriate profile 

533 also supports (i.e. "RGB", "RGBA", "CMYK", etc.) 

534 :param outMode: String, as a valid PIL mode that the appropriate profile 

535 also supports (i.e. "RGB", "RGBA", "CMYK", etc.) 

536 :param renderingIntent: Integer (0-3) specifying the rendering intent you 

537 wish to use for the transform 

538 

539 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

540 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

541 ImageCms.Intent.SATURATION = 2 

542 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

543 

544 see the pyCMS documentation for details on rendering intents and what 

545 they do. 

546 :param flags: Integer (0-...) specifying additional flags 

547 :returns: A CmsTransform class object. 

548 :exception PyCMSError: 

549 """ 

550 

551 if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): 

552 msg = "renderingIntent must be an integer between 0 and 3" 

553 raise PyCMSError(msg) 

554 

555 if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): 

556 msg = f"flags must be an integer between 0 and {_MAX_FLAG}" 

557 raise PyCMSError(msg) 

558 

559 try: 

560 if not isinstance(inputProfile, ImageCmsProfile): 

561 inputProfile = ImageCmsProfile(inputProfile) 

562 if not isinstance(outputProfile, ImageCmsProfile): 

563 outputProfile = ImageCmsProfile(outputProfile) 

564 return ImageCmsTransform( 

565 inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags 

566 ) 

567 except (OSError, TypeError, ValueError) as v: 

568 raise PyCMSError(v) from v 

569 

570 

571def buildProofTransform( 

572 inputProfile: _CmsProfileCompatible, 

573 outputProfile: _CmsProfileCompatible, 

574 proofProfile: _CmsProfileCompatible, 

575 inMode: str, 

576 outMode: str, 

577 renderingIntent: Intent = Intent.PERCEPTUAL, 

578 proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC, 

579 flags: Flags = Flags.SOFTPROOFING, 

580) -> ImageCmsTransform: 

581 """ 

582 (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the 

583 ``outputProfile``, but tries to simulate the result that would be 

584 obtained on the ``proofProfile`` device. 

585 

586 If the input, output, or proof profiles specified are not valid 

587 filenames, a :exc:`PyCMSError` will be raised. 

588 

589 If an error occurs during creation of the transform, 

590 a :exc:`PyCMSError` will be raised. 

591 

592 If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` 

593 (or by pyCMS), a :exc:`PyCMSError` will be raised. 

594 

595 This function builds and returns an ICC transform from the ``inputProfile`` 

596 to the ``outputProfile``, but tries to simulate the result that would be 

597 obtained on the ``proofProfile`` device using ``renderingIntent`` and 

598 ``proofRenderingIntent`` to determine what to do with out-of-gamut 

599 colors. This is known as "soft-proofing". It will ONLY work for 

600 converting images that are in ``inMode`` to images that are in outMode 

601 color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.). 

602 

603 Usage of the resulting transform object is exactly the same as with 

604 ImageCms.buildTransform(). 

605 

606 Proof profiling is generally used when using an output device to get a 

607 good idea of what the final printed/displayed image would look like on 

608 the ``proofProfile`` device when it's quicker and easier to use the 

609 output device for judging color. Generally, this means that the 

610 output device is a monitor, or a dye-sub printer (etc.), and the simulated 

611 device is something more expensive, complicated, or time consuming 

612 (making it difficult to make a real print for color judgement purposes). 

613 

614 Soft-proofing basically functions by adjusting the colors on the 

615 output device to match the colors of the device being simulated. However, 

616 when the simulated device has a much wider gamut than the output 

617 device, you may obtain marginal results. 

618 

619 :param inputProfile: String, as a valid filename path to the ICC input 

620 profile you wish to use for this transform, or a profile object 

621 :param outputProfile: String, as a valid filename path to the ICC output 

622 (monitor, usually) profile you wish to use for this transform, or a 

623 profile object 

624 :param proofProfile: String, as a valid filename path to the ICC proof 

625 profile you wish to use for this transform, or a profile object 

626 :param inMode: String, as a valid PIL mode that the appropriate profile 

627 also supports (i.e. "RGB", "RGBA", "CMYK", etc.) 

628 :param outMode: String, as a valid PIL mode that the appropriate profile 

629 also supports (i.e. "RGB", "RGBA", "CMYK", etc.) 

630 :param renderingIntent: Integer (0-3) specifying the rendering intent you 

631 wish to use for the input->proof (simulated) transform 

632 

633 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

634 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

635 ImageCms.Intent.SATURATION = 2 

636 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

637 

638 see the pyCMS documentation for details on rendering intents and what 

639 they do. 

640 :param proofRenderingIntent: Integer (0-3) specifying the rendering intent 

641 you wish to use for proof->output transform 

642 

643 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

644 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

645 ImageCms.Intent.SATURATION = 2 

646 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

647 

648 see the pyCMS documentation for details on rendering intents and what 

649 they do. 

650 :param flags: Integer (0-...) specifying additional flags 

651 :returns: A CmsTransform class object. 

652 :exception PyCMSError: 

653 """ 

654 

655 if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): 

656 msg = "renderingIntent must be an integer between 0 and 3" 

657 raise PyCMSError(msg) 

658 

659 if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): 

660 msg = f"flags must be an integer between 0 and {_MAX_FLAG}" 

661 raise PyCMSError(msg) 

662 

663 try: 

664 if not isinstance(inputProfile, ImageCmsProfile): 

665 inputProfile = ImageCmsProfile(inputProfile) 

666 if not isinstance(outputProfile, ImageCmsProfile): 

667 outputProfile = ImageCmsProfile(outputProfile) 

668 if not isinstance(proofProfile, ImageCmsProfile): 

669 proofProfile = ImageCmsProfile(proofProfile) 

670 return ImageCmsTransform( 

671 inputProfile, 

672 outputProfile, 

673 inMode, 

674 outMode, 

675 renderingIntent, 

676 proofProfile, 

677 proofRenderingIntent, 

678 flags, 

679 ) 

680 except (OSError, TypeError, ValueError) as v: 

681 raise PyCMSError(v) from v 

682 

683 

684buildTransformFromOpenProfiles = buildTransform 

685buildProofTransformFromOpenProfiles = buildProofTransform 

686 

687 

688def applyTransform( 

689 im: Image.Image, transform: ImageCmsTransform, inPlace: bool = False 

690) -> Image.Image | None: 

691 """ 

692 (pyCMS) Applies a transform to a given image. 

693 

694 If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised. 

695 

696 If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a 

697 :exc:`PyCMSError` is raised. 

698 

699 If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not 

700 supported by pyCMSdll or the profiles you used for the transform, a 

701 :exc:`PyCMSError` is raised. 

702 

703 If an error occurs while the transform is being applied, 

704 a :exc:`PyCMSError` is raised. 

705 

706 This function applies a pre-calculated transform (from 

707 ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) 

708 to an image. The transform can be used for multiple images, saving 

709 considerable calculation time if doing the same conversion multiple times. 

710 

711 If you want to modify im in-place instead of receiving a new image as 

712 the return value, set ``inPlace`` to ``True``. This can only be done if 

713 ``transform.input_mode`` and ``transform.output_mode`` are the same, because we 

714 can't change the mode in-place (the buffer sizes for some modes are 

715 different). The default behavior is to return a new :py:class:`~PIL.Image.Image` 

716 object of the same dimensions in mode ``transform.output_mode``. 

717 

718 :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same 

719 as the ``input_mode`` supported by the transform. 

720 :param transform: A valid CmsTransform class object 

721 :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is 

722 returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the 

723 transform applied is returned (and ``im`` is not changed). The default is 

724 ``False``. 

725 :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object, 

726 depending on the value of ``inPlace``. The profile will be returned in 

727 the image's ``info['icc_profile']``. 

728 :exception PyCMSError: 

729 """ 

730 

731 try: 

732 if inPlace: 

733 transform.apply_in_place(im) 

734 imOut = None 

735 else: 

736 imOut = transform.apply(im) 

737 except (TypeError, ValueError) as v: 

738 raise PyCMSError(v) from v 

739 

740 return imOut 

741 

742 

743def createProfile( 

744 colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0 

745) -> core.CmsProfile: 

746 """ 

747 (pyCMS) Creates a profile. 

748 

749 If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, 

750 a :exc:`PyCMSError` is raised. 

751 

752 If using LAB and ``colorTemp`` is not a positive integer, 

753 a :exc:`PyCMSError` is raised. 

754 

755 If an error occurs while creating the profile, 

756 a :exc:`PyCMSError` is raised. 

757 

758 Use this function to create common profiles on-the-fly instead of 

759 having to supply a profile on disk and knowing the path to it. It 

760 returns a normal CmsProfile object that can be passed to 

761 ImageCms.buildTransformFromOpenProfiles() to create a transform to apply 

762 to images. 

763 

764 :param colorSpace: String, the color space of the profile you wish to 

765 create. 

766 Currently only "LAB", "XYZ", and "sRGB" are supported. 

767 :param colorTemp: Positive number for the white point for the profile, in 

768 degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 

769 illuminant if omitted (5000k). colorTemp is ONLY applied to LAB 

770 profiles, and is ignored for XYZ and sRGB. 

771 :returns: A CmsProfile class object 

772 :exception PyCMSError: 

773 """ 

774 

775 if colorSpace not in ["LAB", "XYZ", "sRGB"]: 

776 msg = ( 

777 f"Color space not supported for on-the-fly profile creation ({colorSpace})" 

778 ) 

779 raise PyCMSError(msg) 

780 

781 if colorSpace == "LAB": 

782 try: 

783 colorTemp = float(colorTemp) 

784 except (TypeError, ValueError) as e: 

785 msg = f'Color temperature must be numeric, "{colorTemp}" not valid' 

786 raise PyCMSError(msg) from e 

787 

788 try: 

789 return core.createProfile(colorSpace, colorTemp) 

790 except (TypeError, ValueError) as v: 

791 raise PyCMSError(v) from v 

792 

793 

794def getProfileName(profile: _CmsProfileCompatible) -> str: 

795 """ 

796 

797 (pyCMS) Gets the internal product name for the given profile. 

798 

799 If ``profile`` isn't a valid CmsProfile object or filename to a profile, 

800 a :exc:`PyCMSError` is raised If an error occurs while trying 

801 to obtain the name tag, a :exc:`PyCMSError` is raised. 

802 

803 Use this function to obtain the INTERNAL name of the profile (stored 

804 in an ICC tag in the profile itself), usually the one used when the 

805 profile was originally created. Sometimes this tag also contains 

806 additional information supplied by the creator. 

807 

808 :param profile: EITHER a valid CmsProfile object, OR a string of the 

809 filename of an ICC profile. 

810 :returns: A string containing the internal name of the profile as stored 

811 in an ICC tag. 

812 :exception PyCMSError: 

813 """ 

814 

815 try: 

816 # add an extra newline to preserve pyCMS compatibility 

817 if not isinstance(profile, ImageCmsProfile): 

818 profile = ImageCmsProfile(profile) 

819 # do it in python, not c. 

820 # // name was "%s - %s" (model, manufacturer) || Description , 

821 # // but if the Model and Manufacturer were the same or the model 

822 # // was long, Just the model, in 1.x 

823 model = profile.profile.model 

824 manufacturer = profile.profile.manufacturer 

825 

826 if not (model or manufacturer): 

827 return (profile.profile.profile_description or "") + "\n" 

828 if not manufacturer or (model and len(model) > 30): 

829 return f"{model}\n" 

830 return f"{model} - {manufacturer}\n" 

831 

832 except (AttributeError, OSError, TypeError, ValueError) as v: 

833 raise PyCMSError(v) from v 

834 

835 

836def getProfileInfo(profile: _CmsProfileCompatible) -> str: 

837 """ 

838 (pyCMS) Gets the internal product information for the given profile. 

839 

840 If ``profile`` isn't a valid CmsProfile object or filename to a profile, 

841 a :exc:`PyCMSError` is raised. 

842 

843 If an error occurs while trying to obtain the info tag, 

844 a :exc:`PyCMSError` is raised. 

845 

846 Use this function to obtain the information stored in the profile's 

847 info tag. This often contains details about the profile, and how it 

848 was created, as supplied by the creator. 

849 

850 :param profile: EITHER a valid CmsProfile object, OR a string of the 

851 filename of an ICC profile. 

852 :returns: A string containing the internal profile information stored in 

853 an ICC tag. 

854 :exception PyCMSError: 

855 """ 

856 

857 try: 

858 if not isinstance(profile, ImageCmsProfile): 

859 profile = ImageCmsProfile(profile) 

860 # add an extra newline to preserve pyCMS compatibility 

861 # Python, not C. the white point bits weren't working well, 

862 # so skipping. 

863 # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint 

864 description = profile.profile.profile_description 

865 cpright = profile.profile.copyright 

866 elements = [element for element in (description, cpright) if element] 

867 return "\r\n\r\n".join(elements) + "\r\n\r\n" 

868 

869 except (AttributeError, OSError, TypeError, ValueError) as v: 

870 raise PyCMSError(v) from v 

871 

872 

873def getProfileCopyright(profile: _CmsProfileCompatible) -> str: 

874 """ 

875 (pyCMS) Gets the copyright for the given profile. 

876 

877 If ``profile`` isn't a valid CmsProfile object or filename to a profile, a 

878 :exc:`PyCMSError` is raised. 

879 

880 If an error occurs while trying to obtain the copyright tag, 

881 a :exc:`PyCMSError` is raised. 

882 

883 Use this function to obtain the information stored in the profile's 

884 copyright tag. 

885 

886 :param profile: EITHER a valid CmsProfile object, OR a string of the 

887 filename of an ICC profile. 

888 :returns: A string containing the internal profile information stored in 

889 an ICC tag. 

890 :exception PyCMSError: 

891 """ 

892 try: 

893 # add an extra newline to preserve pyCMS compatibility 

894 if not isinstance(profile, ImageCmsProfile): 

895 profile = ImageCmsProfile(profile) 

896 return (profile.profile.copyright or "") + "\n" 

897 except (AttributeError, OSError, TypeError, ValueError) as v: 

898 raise PyCMSError(v) from v 

899 

900 

901def getProfileManufacturer(profile: _CmsProfileCompatible) -> str: 

902 """ 

903 (pyCMS) Gets the manufacturer for the given profile. 

904 

905 If ``profile`` isn't a valid CmsProfile object or filename to a profile, a 

906 :exc:`PyCMSError` is raised. 

907 

908 If an error occurs while trying to obtain the manufacturer tag, a 

909 :exc:`PyCMSError` is raised. 

910 

911 Use this function to obtain the information stored in the profile's 

912 manufacturer tag. 

913 

914 :param profile: EITHER a valid CmsProfile object, OR a string of the 

915 filename of an ICC profile. 

916 :returns: A string containing the internal profile information stored in 

917 an ICC tag. 

918 :exception PyCMSError: 

919 """ 

920 try: 

921 # add an extra newline to preserve pyCMS compatibility 

922 if not isinstance(profile, ImageCmsProfile): 

923 profile = ImageCmsProfile(profile) 

924 return (profile.profile.manufacturer or "") + "\n" 

925 except (AttributeError, OSError, TypeError, ValueError) as v: 

926 raise PyCMSError(v) from v 

927 

928 

929def getProfileModel(profile: _CmsProfileCompatible) -> str: 

930 """ 

931 (pyCMS) Gets the model for the given profile. 

932 

933 If ``profile`` isn't a valid CmsProfile object or filename to a profile, a 

934 :exc:`PyCMSError` is raised. 

935 

936 If an error occurs while trying to obtain the model tag, 

937 a :exc:`PyCMSError` is raised. 

938 

939 Use this function to obtain the information stored in the profile's 

940 model tag. 

941 

942 :param profile: EITHER a valid CmsProfile object, OR a string of the 

943 filename of an ICC profile. 

944 :returns: A string containing the internal profile information stored in 

945 an ICC tag. 

946 :exception PyCMSError: 

947 """ 

948 

949 try: 

950 # add an extra newline to preserve pyCMS compatibility 

951 if not isinstance(profile, ImageCmsProfile): 

952 profile = ImageCmsProfile(profile) 

953 return (profile.profile.model or "") + "\n" 

954 except (AttributeError, OSError, TypeError, ValueError) as v: 

955 raise PyCMSError(v) from v 

956 

957 

958def getProfileDescription(profile: _CmsProfileCompatible) -> str: 

959 """ 

960 (pyCMS) Gets the description for the given profile. 

961 

962 If ``profile`` isn't a valid CmsProfile object or filename to a profile, a 

963 :exc:`PyCMSError` is raised. 

964 

965 If an error occurs while trying to obtain the description tag, 

966 a :exc:`PyCMSError` is raised. 

967 

968 Use this function to obtain the information stored in the profile's 

969 description tag. 

970 

971 :param profile: EITHER a valid CmsProfile object, OR a string of the 

972 filename of an ICC profile. 

973 :returns: A string containing the internal profile information stored in an 

974 ICC tag. 

975 :exception PyCMSError: 

976 """ 

977 

978 try: 

979 # add an extra newline to preserve pyCMS compatibility 

980 if not isinstance(profile, ImageCmsProfile): 

981 profile = ImageCmsProfile(profile) 

982 return (profile.profile.profile_description or "") + "\n" 

983 except (AttributeError, OSError, TypeError, ValueError) as v: 

984 raise PyCMSError(v) from v 

985 

986 

987def getDefaultIntent(profile: _CmsProfileCompatible) -> int: 

988 """ 

989 (pyCMS) Gets the default intent name for the given profile. 

990 

991 If ``profile`` isn't a valid CmsProfile object or filename to a profile, a 

992 :exc:`PyCMSError` is raised. 

993 

994 If an error occurs while trying to obtain the default intent, a 

995 :exc:`PyCMSError` is raised. 

996 

997 Use this function to determine the default (and usually best optimized) 

998 rendering intent for this profile. Most profiles support multiple 

999 rendering intents, but are intended mostly for one type of conversion. 

1000 If you wish to use a different intent than returned, use 

1001 ImageCms.isIntentSupported() to verify it will work first. 

1002 

1003 :param profile: EITHER a valid CmsProfile object, OR a string of the 

1004 filename of an ICC profile. 

1005 :returns: Integer 0-3 specifying the default rendering intent for this 

1006 profile. 

1007 

1008 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

1009 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

1010 ImageCms.Intent.SATURATION = 2 

1011 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

1012 

1013 see the pyCMS documentation for details on rendering intents and what 

1014 they do. 

1015 :exception PyCMSError: 

1016 """ 

1017 

1018 try: 

1019 if not isinstance(profile, ImageCmsProfile): 

1020 profile = ImageCmsProfile(profile) 

1021 return profile.profile.rendering_intent 

1022 except (AttributeError, OSError, TypeError, ValueError) as v: 

1023 raise PyCMSError(v) from v 

1024 

1025 

1026def isIntentSupported( 

1027 profile: _CmsProfileCompatible, intent: Intent, direction: Direction 

1028) -> Literal[-1, 1]: 

1029 """ 

1030 (pyCMS) Checks if a given intent is supported. 

1031 

1032 Use this function to verify that you can use your desired 

1033 ``intent`` with ``profile``, and that ``profile`` can be used for the 

1034 input/output/proof profile as you desire. 

1035 

1036 Some profiles are created specifically for one "direction", can cannot 

1037 be used for others. Some profiles can only be used for certain 

1038 rendering intents, so it's best to either verify this before trying 

1039 to create a transform with them (using this function), or catch the 

1040 potential :exc:`PyCMSError` that will occur if they don't 

1041 support the modes you select. 

1042 

1043 :param profile: EITHER a valid CmsProfile object, OR a string of the 

1044 filename of an ICC profile. 

1045 :param intent: Integer (0-3) specifying the rendering intent you wish to 

1046 use with this profile 

1047 

1048 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

1049 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

1050 ImageCms.Intent.SATURATION = 2 

1051 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

1052 

1053 see the pyCMS documentation for details on rendering intents and what 

1054 they do. 

1055 :param direction: Integer specifying if the profile is to be used for 

1056 input, output, or proof 

1057 

1058 INPUT = 0 (or use ImageCms.Direction.INPUT) 

1059 OUTPUT = 1 (or use ImageCms.Direction.OUTPUT) 

1060 PROOF = 2 (or use ImageCms.Direction.PROOF) 

1061 

1062 :returns: 1 if the intent/direction are supported, -1 if they are not. 

1063 :exception PyCMSError: 

1064 """ 

1065 

1066 try: 

1067 if not isinstance(profile, ImageCmsProfile): 

1068 profile = ImageCmsProfile(profile) 

1069 # FIXME: I get different results for the same data w. different 

1070 # compilers. Bug in LittleCMS or in the binding? 

1071 if profile.profile.is_intent_supported(intent, direction): 

1072 return 1 

1073 else: 

1074 return -1 

1075 except (AttributeError, OSError, TypeError, ValueError) as v: 

1076 raise PyCMSError(v) from v