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

305 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, __version__ 

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 

111def __getattr__(name: str) -> Any: 

112 if name == "DESCRIPTION": 

113 deprecate("PIL.ImageCms.DESCRIPTION", 12) 

114 return _DESCRIPTION 

115 elif name == "VERSION": 

116 deprecate("PIL.ImageCms.VERSION", 12) 

117 return _VERSION 

118 elif name == "FLAGS": 

119 deprecate("PIL.ImageCms.FLAGS", 12, "PIL.ImageCms.Flags") 

120 return _FLAGS 

121 msg = f"module '{__name__}' has no attribute '{name}'" 

122 raise AttributeError(msg) 

123 

124 

125# --------------------------------------------------------------------. 

126 

127 

128# 

129# intent/direction values 

130 

131 

132class Intent(IntEnum): 

133 PERCEPTUAL = 0 

134 RELATIVE_COLORIMETRIC = 1 

135 SATURATION = 2 

136 ABSOLUTE_COLORIMETRIC = 3 

137 

138 

139class Direction(IntEnum): 

140 INPUT = 0 

141 OUTPUT = 1 

142 PROOF = 2 

143 

144 

145# 

146# flags 

147 

148 

149class Flags(IntFlag): 

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

151 

152 NONE = 0 

153 NOCACHE = 0x0040 

154 """Inhibit 1-pixel cache""" 

155 NOOPTIMIZE = 0x0100 

156 """Inhibit optimizations""" 

157 NULLTRANSFORM = 0x0200 

158 """Don't transform anyway""" 

159 GAMUTCHECK = 0x1000 

160 """Out of Gamut alarm""" 

161 SOFTPROOFING = 0x4000 

162 """Do softproofing""" 

163 BLACKPOINTCOMPENSATION = 0x2000 

164 NOWHITEONWHITEFIXUP = 0x0004 

165 """Don't fix scum dot""" 

166 HIGHRESPRECALC = 0x0400 

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

168 LOWRESPRECALC = 0x0800 

169 """Use less memory to minimize resources""" 

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

171 USE_8BITS_DEVICELINK = 0x0008 

172 """Create 8 bits devicelinks""" 

173 GUESSDEVICECLASS = 0x0020 

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

175 KEEP_SEQUENCE = 0x0080 

176 """Keep profile sequence for devicelink creation""" 

177 FORCE_CLUT = 0x0002 

178 """Force CLUT optimization""" 

179 CLUT_POST_LINEARIZATION = 0x0001 

180 """create postlinearization tables if possible""" 

181 CLUT_PRE_LINEARIZATION = 0x0010 

182 """create prelinearization tables if possible""" 

183 NONEGATIVES = 0x8000 

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

185 COPY_ALPHA = 0x04000000 

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

187 NODEFAULTRESOURCEDEF = 0x01000000 

188 

189 _GRIDPOINTS_1 = 1 << 16 

190 _GRIDPOINTS_2 = 2 << 16 

191 _GRIDPOINTS_4 = 4 << 16 

192 _GRIDPOINTS_8 = 8 << 16 

193 _GRIDPOINTS_16 = 16 << 16 

194 _GRIDPOINTS_32 = 32 << 16 

195 _GRIDPOINTS_64 = 64 << 16 

196 _GRIDPOINTS_128 = 128 << 16 

197 

198 @staticmethod 

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

200 """ 

201 Fine-tune control over number of gridpoints 

202 

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

204 """ 

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

206 

207 

208_MAX_FLAG = reduce(operator.or_, Flags) 

209 

210 

211_FLAGS = { 

212 "MATRIXINPUT": 1, 

213 "MATRIXOUTPUT": 2, 

214 "MATRIXONLY": (1 | 2), 

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

216 # Don't create prelinearization tables on precalculated transforms 

217 # (internal use): 

218 "NOPRELINEARIZATION": 16, 

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

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

221 "NOTPRECALC": 256, 

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

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

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

225 "WHITEBLACKCOMPENSATION": 8192, 

226 "BLACKPOINTCOMPENSATION": 8192, 

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

228 "SOFTPROOFING": 16384, # Do softproofing 

229 "PRESERVEBLACK": 32768, # Black preservation 

230 "NODEFAULTRESOURCEDEF": 16777216, # CRD special 

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

232} 

233 

234 

235# --------------------------------------------------------------------. 

236# Experimental PIL-level API 

237# --------------------------------------------------------------------. 

238 

239## 

240# Profile. 

241 

242 

243class ImageCmsProfile: 

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

245 """ 

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

247 a file like object containing a profile or a 

248 low-level profile object 

249 

250 """ 

251 self.filename = None 

252 self.product_name = None # profile.product_name 

253 self.product_info = None # profile.product_info 

254 

255 if isinstance(profile, str): 

256 if sys.platform == "win32": 

257 profile_bytes_path = profile.encode() 

258 try: 

259 profile_bytes_path.decode("ascii") 

260 except UnicodeDecodeError: 

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

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

263 return 

264 self.filename = profile 

265 self.profile = core.profile_open(profile) 

266 elif hasattr(profile, "read"): 

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

268 elif isinstance(profile, core.CmsProfile): 

269 self.profile = profile 

270 else: 

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

272 raise TypeError(msg) 

273 

274 def tobytes(self) -> bytes: 

275 """ 

276 Returns the profile in a format suitable for embedding in 

277 saved images. 

278 

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

280 """ 

281 

282 return core.profile_tobytes(self.profile) 

283 

284 

285class ImageCmsTransform(Image.ImagePointHandler): 

286 """ 

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

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

289 

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

291 """ 

292 

293 def __init__( 

294 self, 

295 input: ImageCmsProfile, 

296 output: ImageCmsProfile, 

297 input_mode: str, 

298 output_mode: str, 

299 intent: Intent = Intent.PERCEPTUAL, 

300 proof: ImageCmsProfile | None = None, 

301 proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC, 

302 flags: Flags = Flags.NONE, 

303 ): 

304 supported_modes = ( 

305 "RGB", 

306 "RGBA", 

307 "RGBX", 

308 "CMYK", 

309 "I;16", 

310 "I;16L", 

311 "I;16B", 

312 "YCbCr", 

313 "LAB", 

314 "L", 

315 "1", 

316 ) 

317 for mode in (input_mode, output_mode): 

318 if mode not in supported_modes: 

319 deprecate( 

320 mode, 

321 12, 

322 { 

323 "L;16": "I;16 or I;16L", 

324 "L:16B": "I;16B", 

325 "YCCA": "YCbCr", 

326 "YCC": "YCbCr", 

327 }.get(mode), 

328 ) 

329 if proof is None: 

330 self.transform = core.buildTransform( 

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

332 ) 

333 else: 

334 self.transform = core.buildProofTransform( 

335 input.profile, 

336 output.profile, 

337 proof.profile, 

338 input_mode, 

339 output_mode, 

340 intent, 

341 proof_intent, 

342 flags, 

343 ) 

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

345 self.input_mode = self.inputMode = input_mode 

346 self.output_mode = self.outputMode = output_mode 

347 

348 self.output_profile = output 

349 

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

351 return self.apply(im) 

352 

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

354 if imOut is None: 

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

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

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

358 return imOut 

359 

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

361 if im.mode != self.output_mode: 

362 msg = "mode mismatch" 

363 raise ValueError(msg) # wrong output mode 

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

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

366 return im 

367 

368 

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

370 """ 

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

372 

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

374 """ 

375 

376 if sys.platform != "win32": 

377 return None 

378 

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

380 

381 if isinstance(handle, ImageWin.HDC): 

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

383 else: 

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

385 if profile is None: 

386 return None 

387 return ImageCmsProfile(profile) 

388 

389 

390# --------------------------------------------------------------------. 

391# pyCMS compatible layer 

392# --------------------------------------------------------------------. 

393 

394 

395class PyCMSError(Exception): 

396 """(pyCMS) Exception class. 

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

398 

399 pass 

400 

401 

402def profileToProfile( 

403 im: Image.Image, 

404 inputProfile: _CmsProfileCompatible, 

405 outputProfile: _CmsProfileCompatible, 

406 renderingIntent: Intent = Intent.PERCEPTUAL, 

407 outputMode: str | None = None, 

408 inPlace: bool = False, 

409 flags: Flags = Flags.NONE, 

410) -> Image.Image | None: 

411 """ 

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

413 ``inputProfile`` to ``outputProfile``. 

414 

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

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

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

418 If an error occurs during application of the profiles, 

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

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

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

422 

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

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

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

426 

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

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

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

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

431 profile must handle CMYK data. 

432 

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

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

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

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

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

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

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

440 wish to use for the transform 

441 

442 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

443 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

444 ImageCms.Intent.SATURATION = 2 

445 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

446 

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

448 they do. 

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

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

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

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

453 image (im.mode) 

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

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

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

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

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

459 the value of ``inPlace`` 

460 :exception PyCMSError: 

461 """ 

462 

463 if outputMode is None: 

464 outputMode = im.mode 

465 

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

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

468 raise PyCMSError(msg) 

469 

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

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

472 raise PyCMSError(msg) 

473 

474 try: 

475 if not isinstance(inputProfile, ImageCmsProfile): 

476 inputProfile = ImageCmsProfile(inputProfile) 

477 if not isinstance(outputProfile, ImageCmsProfile): 

478 outputProfile = ImageCmsProfile(outputProfile) 

479 transform = ImageCmsTransform( 

480 inputProfile, 

481 outputProfile, 

482 im.mode, 

483 outputMode, 

484 renderingIntent, 

485 flags=flags, 

486 ) 

487 if inPlace: 

488 transform.apply_in_place(im) 

489 imOut = None 

490 else: 

491 imOut = transform.apply(im) 

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

493 raise PyCMSError(v) from v 

494 

495 return imOut 

496 

497 

498def getOpenProfile( 

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

500) -> ImageCmsProfile: 

501 """ 

502 (pyCMS) Opens an ICC profile file. 

503 

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

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

506 

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

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

509 

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

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

512 :returns: A CmsProfile class object. 

513 :exception PyCMSError: 

514 """ 

515 

516 try: 

517 return ImageCmsProfile(profileFilename) 

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

519 raise PyCMSError(v) from v 

520 

521 

522def buildTransform( 

523 inputProfile: _CmsProfileCompatible, 

524 outputProfile: _CmsProfileCompatible, 

525 inMode: str, 

526 outMode: str, 

527 renderingIntent: Intent = Intent.PERCEPTUAL, 

528 flags: Flags = Flags.NONE, 

529) -> ImageCmsTransform: 

530 """ 

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

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

533 image. 

534 

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

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

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

538 

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

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

541 

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

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

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

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

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

547 

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

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

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

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

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

553 the lookup table for the transform. 

554 

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

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

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

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

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

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

561 

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

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

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

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

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

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

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

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

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

571 wish to use for the transform 

572 

573 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

574 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

575 ImageCms.Intent.SATURATION = 2 

576 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

577 

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

579 they do. 

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

581 :returns: A CmsTransform class object. 

582 :exception PyCMSError: 

583 """ 

584 

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

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

587 raise PyCMSError(msg) 

588 

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

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

591 raise PyCMSError(msg) 

592 

593 try: 

594 if not isinstance(inputProfile, ImageCmsProfile): 

595 inputProfile = ImageCmsProfile(inputProfile) 

596 if not isinstance(outputProfile, ImageCmsProfile): 

597 outputProfile = ImageCmsProfile(outputProfile) 

598 return ImageCmsTransform( 

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

600 ) 

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

602 raise PyCMSError(v) from v 

603 

604 

605def buildProofTransform( 

606 inputProfile: _CmsProfileCompatible, 

607 outputProfile: _CmsProfileCompatible, 

608 proofProfile: _CmsProfileCompatible, 

609 inMode: str, 

610 outMode: str, 

611 renderingIntent: Intent = Intent.PERCEPTUAL, 

612 proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC, 

613 flags: Flags = Flags.SOFTPROOFING, 

614) -> ImageCmsTransform: 

615 """ 

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

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

618 obtained on the ``proofProfile`` device. 

619 

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

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

622 

623 If an error occurs during creation of the transform, 

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

625 

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

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

628 

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

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

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

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

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

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

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

636 

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

638 ImageCms.buildTransform(). 

639 

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

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

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

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

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

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

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

647 

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

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

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

651 device, you may obtain marginal results. 

652 

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

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

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

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

657 profile object 

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

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

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

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

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

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

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

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

666 

667 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

668 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

669 ImageCms.Intent.SATURATION = 2 

670 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

671 

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

673 they do. 

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

675 you wish to use for proof->output transform 

676 

677 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

678 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

679 ImageCms.Intent.SATURATION = 2 

680 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

681 

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

683 they do. 

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

685 :returns: A CmsTransform class object. 

686 :exception PyCMSError: 

687 """ 

688 

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

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

691 raise PyCMSError(msg) 

692 

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

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

695 raise PyCMSError(msg) 

696 

697 try: 

698 if not isinstance(inputProfile, ImageCmsProfile): 

699 inputProfile = ImageCmsProfile(inputProfile) 

700 if not isinstance(outputProfile, ImageCmsProfile): 

701 outputProfile = ImageCmsProfile(outputProfile) 

702 if not isinstance(proofProfile, ImageCmsProfile): 

703 proofProfile = ImageCmsProfile(proofProfile) 

704 return ImageCmsTransform( 

705 inputProfile, 

706 outputProfile, 

707 inMode, 

708 outMode, 

709 renderingIntent, 

710 proofProfile, 

711 proofRenderingIntent, 

712 flags, 

713 ) 

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

715 raise PyCMSError(v) from v 

716 

717 

718buildTransformFromOpenProfiles = buildTransform 

719buildProofTransformFromOpenProfiles = buildProofTransform 

720 

721 

722def applyTransform( 

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

724) -> Image.Image | None: 

725 """ 

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

727 

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

729 

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

731 :exc:`PyCMSError` is raised. 

732 

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

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

735 :exc:`PyCMSError` is raised. 

736 

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

738 a :exc:`PyCMSError` is raised. 

739 

740 This function applies a pre-calculated transform (from 

741 ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) 

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

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

744 

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

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

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

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

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

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

751 

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

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

754 :param transform: A valid CmsTransform class object 

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

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

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

758 ``False``. 

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

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

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

762 :exception PyCMSError: 

763 """ 

764 

765 try: 

766 if inPlace: 

767 transform.apply_in_place(im) 

768 imOut = None 

769 else: 

770 imOut = transform.apply(im) 

771 except (TypeError, ValueError) as v: 

772 raise PyCMSError(v) from v 

773 

774 return imOut 

775 

776 

777def createProfile( 

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

779) -> core.CmsProfile: 

780 """ 

781 (pyCMS) Creates a profile. 

782 

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

784 a :exc:`PyCMSError` is raised. 

785 

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

787 a :exc:`PyCMSError` is raised. 

788 

789 If an error occurs while creating the profile, 

790 a :exc:`PyCMSError` is raised. 

791 

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

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

794 returns a normal CmsProfile object that can be passed to 

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

796 to images. 

797 

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

799 create. 

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

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

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

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

804 profiles, and is ignored for XYZ and sRGB. 

805 :returns: A CmsProfile class object 

806 :exception PyCMSError: 

807 """ 

808 

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

810 msg = ( 

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

812 ) 

813 raise PyCMSError(msg) 

814 

815 if colorSpace == "LAB": 

816 try: 

817 colorTemp = float(colorTemp) 

818 except (TypeError, ValueError) as e: 

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

820 raise PyCMSError(msg) from e 

821 

822 try: 

823 return core.createProfile(colorSpace, colorTemp) 

824 except (TypeError, ValueError) as v: 

825 raise PyCMSError(v) from v 

826 

827 

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

829 """ 

830 

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

832 

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

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

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

836 

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

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

839 profile was originally created. Sometimes this tag also contains 

840 additional information supplied by the creator. 

841 

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

843 filename of an ICC profile. 

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

845 in an ICC tag. 

846 :exception PyCMSError: 

847 """ 

848 

849 try: 

850 # add an extra newline to preserve pyCMS compatibility 

851 if not isinstance(profile, ImageCmsProfile): 

852 profile = ImageCmsProfile(profile) 

853 # do it in python, not c. 

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

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

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

857 model = profile.profile.model 

858 manufacturer = profile.profile.manufacturer 

859 

860 if not (model or manufacturer): 

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

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

863 return f"{model}\n" 

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

865 

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

867 raise PyCMSError(v) from v 

868 

869 

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

871 """ 

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

873 

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

875 a :exc:`PyCMSError` is raised. 

876 

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

878 a :exc:`PyCMSError` is raised. 

879 

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

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

882 was created, as supplied by the creator. 

883 

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

885 filename of an ICC profile. 

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

887 an ICC tag. 

888 :exception PyCMSError: 

889 """ 

890 

891 try: 

892 if not isinstance(profile, ImageCmsProfile): 

893 profile = ImageCmsProfile(profile) 

894 # add an extra newline to preserve pyCMS compatibility 

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

896 # so skipping. 

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

898 description = profile.profile.profile_description 

899 cpright = profile.profile.copyright 

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

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

902 

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

904 raise PyCMSError(v) from v 

905 

906 

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

908 """ 

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

910 

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

912 :exc:`PyCMSError` is raised. 

913 

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

915 a :exc:`PyCMSError` is raised. 

916 

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

918 copyright tag. 

919 

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

921 filename of an ICC profile. 

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

923 an ICC tag. 

924 :exception PyCMSError: 

925 """ 

926 try: 

927 # add an extra newline to preserve pyCMS compatibility 

928 if not isinstance(profile, ImageCmsProfile): 

929 profile = ImageCmsProfile(profile) 

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

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

932 raise PyCMSError(v) from v 

933 

934 

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

936 """ 

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

938 

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

940 :exc:`PyCMSError` is raised. 

941 

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

943 :exc:`PyCMSError` is raised. 

944 

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

946 manufacturer tag. 

947 

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

949 filename of an ICC profile. 

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

951 an ICC tag. 

952 :exception PyCMSError: 

953 """ 

954 try: 

955 # add an extra newline to preserve pyCMS compatibility 

956 if not isinstance(profile, ImageCmsProfile): 

957 profile = ImageCmsProfile(profile) 

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

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

960 raise PyCMSError(v) from v 

961 

962 

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

964 """ 

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

966 

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

968 :exc:`PyCMSError` is raised. 

969 

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

971 a :exc:`PyCMSError` is raised. 

972 

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

974 model tag. 

975 

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

977 filename of an ICC profile. 

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

979 an ICC tag. 

980 :exception PyCMSError: 

981 """ 

982 

983 try: 

984 # add an extra newline to preserve pyCMS compatibility 

985 if not isinstance(profile, ImageCmsProfile): 

986 profile = ImageCmsProfile(profile) 

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

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

989 raise PyCMSError(v) from v 

990 

991 

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

993 """ 

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

995 

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

997 :exc:`PyCMSError` is raised. 

998 

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

1000 a :exc:`PyCMSError` is raised. 

1001 

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

1003 description tag. 

1004 

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

1006 filename of an ICC profile. 

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

1008 ICC tag. 

1009 :exception PyCMSError: 

1010 """ 

1011 

1012 try: 

1013 # add an extra newline to preserve pyCMS compatibility 

1014 if not isinstance(profile, ImageCmsProfile): 

1015 profile = ImageCmsProfile(profile) 

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

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

1018 raise PyCMSError(v) from v 

1019 

1020 

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

1022 """ 

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

1024 

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

1026 :exc:`PyCMSError` is raised. 

1027 

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

1029 :exc:`PyCMSError` is raised. 

1030 

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

1032 rendering intent for this profile. Most profiles support multiple 

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

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

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

1036 

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

1038 filename of an ICC profile. 

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

1040 profile. 

1041 

1042 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

1043 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

1044 ImageCms.Intent.SATURATION = 2 

1045 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

1046 

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

1048 they do. 

1049 :exception PyCMSError: 

1050 """ 

1051 

1052 try: 

1053 if not isinstance(profile, ImageCmsProfile): 

1054 profile = ImageCmsProfile(profile) 

1055 return profile.profile.rendering_intent 

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

1057 raise PyCMSError(v) from v 

1058 

1059 

1060def isIntentSupported( 

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

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

1063 """ 

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

1065 

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

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

1068 input/output/proof profile as you desire. 

1069 

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

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

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

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

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

1075 support the modes you select. 

1076 

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

1078 filename of an ICC profile. 

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

1080 use with this profile 

1081 

1082 ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) 

1083 ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 

1084 ImageCms.Intent.SATURATION = 2 

1085 ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 

1086 

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

1088 they do. 

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

1090 input, output, or proof 

1091 

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

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

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

1095 

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

1097 :exception PyCMSError: 

1098 """ 

1099 

1100 try: 

1101 if not isinstance(profile, ImageCmsProfile): 

1102 profile = ImageCmsProfile(profile) 

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

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

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

1106 return 1 

1107 else: 

1108 return -1 

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

1110 raise PyCMSError(v) from v 

1111 

1112 

1113def versions() -> tuple[str, str | None, str, str]: 

1114 """ 

1115 (pyCMS) Fetches versions. 

1116 """ 

1117 

1118 deprecate( 

1119 "PIL.ImageCms.versions()", 

1120 12, 

1121 '(PIL.features.version("littlecms2"), sys.version, PIL.__version__)', 

1122 ) 

1123 return _VERSION, core.littlecms_version, sys.version.split()[0], __version__