Merge branch 'main' into closed

This commit is contained in:
Andrew Murray 2023-03-09 12:40:17 +11:00
commit 8984b630e3
34 changed files with 308 additions and 144 deletions

View File

@ -3,10 +3,12 @@ name: CIFuzz
on:
push:
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"
- "**.h"
pull_request:
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"
- "**.h"
workflow_dispatch:

52
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Docs
on:
push:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
name: Docs
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: ".ci/*.sh"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install Linux dependencies
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: "3.x"
- name: Build
run: |
.ci/build.sh
- name: Docs
run: |
make doccheck

View File

@ -1,6 +1,15 @@
name: Test Cygwin
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -1,6 +1,15 @@
name: Test Docker
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -1,6 +1,15 @@
name: Test MinGW
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -5,10 +5,12 @@ name: Test Valgrind
on:
push:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
workflow_dispatch:

View File

@ -1,6 +1,15 @@
name: Test Windows
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read

View File

@ -1,6 +1,15 @@
name: Test
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -96,11 +105,6 @@ jobs:
name: errors
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
run: |
make doccheck
- name: After success
run: |
.ci/after_success.sh

View File

@ -177,13 +177,14 @@ class TestEnvVars:
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
assert Image.core.get_block_size() == 2 * 1024 * 1024
def test_warnings(self):
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"}
)
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"}
)
pytest.warns(
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"}
@pytest.mark.parametrize(
"var",
(
{"PILLOW_ALIGNMENT": "15"},
{"PILLOW_BLOCK_SIZE": "1024"},
{"PILLOW_BLOCKS_MAX": "wat"},
),
)
def test_warnings(self, var):
with pytest.warns(UserWarning):
Image._apply_env_variables(var)

View File

@ -36,12 +36,10 @@ class TestDecompressionBomb:
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
def open():
with pytest.warns(Image.DecompressionBombWarning):
with Image.open(TEST_FILE):
pass
pytest.warns(Image.DecompressionBombWarning, open)
def test_exception(self):
# Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
@ -87,7 +85,8 @@ class TestDecompressionCrop:
# same decompression bomb warnings on them.
with hopper() as src:
box = (0, 0, src.width * 2, src.height * 2)
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
with pytest.warns(Image.DecompressionBombWarning):
src.crop(box)
def test_crop_decompression_checks(self):
im = Image.new("RGB", (100, 100))
@ -95,7 +94,8 @@ class TestDecompressionCrop:
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
assert im.crop(value).size == (9, 9)
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
with pytest.warns(Image.DecompressionBombWarning):
im.crop((-160, -160, 99, 99))
with pytest.raises(Image.DecompressionBombError):
im.crop((-99909, -99990, 99999, 99999))

View File

@ -263,13 +263,11 @@ def test_apng_chunk_errors():
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert not im.is_animated
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load()
assert not im.is_animated
pytest.warns(UserWarning, open)
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert not im.is_animated
@ -287,21 +285,17 @@ def test_apng_chunk_errors():
def test_apng_syntax_errors():
def open_frames_zero():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert not im.is_animated
with pytest.raises(OSError):
im.load()
pytest.warns(UserWarning, open_frames_zero)
def open_frames_zero_default():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open_frames_zero_default)
# we can handle this case gracefully
exception = None
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -316,13 +310,11 @@ def test_apng_syntax_errors():
im.seek(im.n_frames - 1)
im.load()
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open)
@pytest.mark.parametrize(
"test_file",

View File

@ -28,7 +28,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(static_test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(TEST_GIF)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -1087,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
im = Image.new("RGB", (1, 1))
im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))]
pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims)
with pytest.warns(UserWarning):
im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info

View File

@ -212,12 +212,10 @@ def test_save_append_images(tmp_path):
def test_unexpected_size():
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/hopper_unexpected.ico") as im:
assert im.size == (16, 16)
pytest.warns(UserWarning, open)
def test_draw_reloaded(tmp_path):
with Image.open(TEST_ICO_FILE) as im:

View File

@ -32,7 +32,8 @@ def test_unclosed_file():
im = Image.open(TEST_IM)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -1065,3 +1065,9 @@ class TestFileLibTiff(LibTiffTestCase):
out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError):
im.save(out, compression=compression)
def test_save_many_compressed(self, tmp_path):
im = hopper()
out = str(tmp_path / "temp.tif")
for _ in range(10000):
im.save(out, compression="jpeg")

View File

@ -42,7 +42,8 @@ def test_unclosed_file():
im = Image.open(test_files[0])
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -27,7 +27,8 @@ def test_unclosed_file():
im = Image.open(test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -25,7 +25,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():

View File

@ -29,11 +29,9 @@ def test_sanity(codec, test_path, format):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
pytest.warns(ResourceWarning, open)
def test_close():
with warnings.catch_warnings():

View File

@ -163,7 +163,9 @@ def test_save_id_section(tmp_path):
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
with pytest.warns(UserWarning):
im.save(out, id_section=id_section)
with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255]

View File

@ -61,7 +61,8 @@ class TestFileTiff:
im = Image.open("Tests/images/multipage.tiff")
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file(self):
with warnings.catch_warnings():
@ -231,7 +232,8 @@ class TestFileTiff:
def test_bad_exif(self):
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
# Should not raise struct.error.
pytest.warns(UserWarning, i._getexif)
with pytest.warns(UserWarning):
i._getexif()
def test_save_rgba(self, tmp_path):
im = hopper("RGBA")

View File

@ -252,7 +252,8 @@ def test_empty_metadata():
head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error.
pytest.warns(UserWarning, info.load, f)
with pytest.warns(UserWarning):
info.load(f)
def test_iccprofile(tmp_path):
@ -418,11 +419,12 @@ def test_too_many_entries():
ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1),
ifd._tagdata[277] = struct.pack("hh", 4, 4)
ifd._tagdata[277] = struct.pack("<hh", 4, 4)
ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError.
pytest.warns(UserWarning, lambda: ifd[277])
with pytest.warns(UserWarning):
assert ifd[277] == 4
def test_tag_group_data():

View File

@ -29,7 +29,10 @@ class TestUnsupportedWebp:
WebPImagePlugin.SUPPORTED = False
file_path = "Tests/images/hopper.webp"
pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path))
with pytest.warns(UserWarning):
with pytest.raises(OSError):
with Image.open(file_path):
pass
if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True

View File

@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
return 1
return tuple(range(1, bands + 1))
def check(self, mode, c=None):
if not c:
c = self.color(mode)
def check(self, mode, expected_color=None):
if not expected_color:
expected_color = self.color(mode)
# check putpixel
im = Image.new(mode, (1, 1), None)
im.putpixel((0, 0), c)
assert (
im.getpixel((0, 0)) == c
), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
im.putpixel((0, 0), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"put/getpixel roundtrip failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check putpixel negative index
im.putpixel((-1, -1), c)
assert (
im.getpixel((-1, -1)) == c
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
im.putpixel((-1, -1), expected_color)
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"put/getpixel roundtrip negative index failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), None)
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
error = ValueError if self._need_cffi_access else IndexError
with pytest.raises(error):
im.putpixel((0, 0), c)
im.putpixel((0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
with pytest.raises(error):
im.putpixel((-1, -1), c)
im.putpixel((-1, -1), expected_color)
with pytest.raises(error):
im.getpixel((-1, -1))
# check initial color
im = Image.new(mode, (1, 1), c)
assert (
im.getpixel((0, 0)) == c
), f"initial color failed for mode {mode}, color {c} "
im = Image.new(mode, (1, 1), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"initial color failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check initial color negative index
assert (
im.getpixel((-1, -1)) == c
), f"initial color failed with negative index for mode {mode}, color {c} "
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"initial color failed with negative index for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), c)
im = Image.new(mode, (0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
self.check(mode)
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
def test_signedness(self, mode):
@pytest.mark.parametrize(
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
)
def test_signedness(self, mode, expected_color):
# see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint*
self.check(mode, 2**15 - 1)
self.check(mode, 2**15)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
self.check(mode, expected_color)
@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))

View File

@ -351,7 +351,8 @@ def test_rotated_transposed_font(font, orientation):
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
# text length is undefined for vertical text
pytest.raises(ValueError, draw.textlength, word)
with pytest.raises(ValueError):
draw.textlength(word)
@pytest.mark.parametrize(
@ -872,25 +873,23 @@ def test_anchor_invalid(font):
d.font = font
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor))
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor))
pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor))
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor)
with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor)
with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor)
with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
for anchor in ["lt", "lb"]:
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))

View File

@ -360,37 +360,20 @@ def test_anchor_invalid_ttb():
d.font = font
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
pytest.raises(
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError,
lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_text(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
# ttb multiline text does not support anchors at all
pytest.raises(
ValueError,
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")

View File

@ -186,8 +186,8 @@ Many of Pillow's features require external libraries:
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
loads libfribidi at runtime if it is installed.
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs)
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-desktop-applications>`_
into a directory listed in the `Dynamic-link library search order (Microsoft Learn)
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-unpackaged-apps>`_
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
See `Build Options`_ to see how to build this version.
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.

View File

@ -338,7 +338,7 @@ Methods
:param fill: Color to use for the fill.
:param width: The line width, in pixels.
:param corners: A tuple of whether to round each corner,
`(top_left, top_right, bottom_right, bottom_left)`.
``(top_left, top_right, bottom_right, bottom_left)``.
.. versionadded:: 8.2.0

View File

@ -0,0 +1,60 @@
9.5.0
-----
Backwards Incompatible Changes
==============================
TODO
^^^^
TODO
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions
=============
Added ``dpi`` argument when saving PDFs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When saving a PDF, resolution could already be specified using the
``resolution`` argument. Now, a tuple of ``(x_resolution, y_resolution)`` can
be provided as ``dpi``. If both are provided, ``dpi`` will override
``resolution``.
Added ``corners`` argument to ``ImageDraw.rounded_rectangle()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`.ImageDraw.rounded_rectangle` now accepts a keyword argument of
``corners``. This a tuple of Booleans, specifying whether to round each corner,
``(top_left, top_right, bottom_right, bottom_left)``.
Security
========
TODO
^^^^
TODO
Other Changes
=============
TODO
^^^^
TODO

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
9.5.0
9.4.0
9.3.0
9.2.0

View File

@ -1850,6 +1850,11 @@ def _save(im, fp, filename):
fp.write(data)
if errcode:
break
if _fp:
try:
os.close(_fp)
except OSError:
pass
if errcode < 0:
msg = f"encoder error {errcode} when writing image file"
raise OSError(msg)

View File

@ -116,7 +116,7 @@ cms_profile_open(PyObject *self, PyObject *args) {
}
static PyObject *
cms_profile_fromstring(PyObject *self, PyObject *args) {
cms_profile_frombytes(PyObject *self, PyObject *args) {
cmsHPROFILE hProfile;
char *pProfile;
@ -960,8 +960,7 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
static PyMethodDef pyCMSdll_methods[] = {
{"profile_open", cms_profile_open, METH_VARARGS},
{"profile_frombytes", cms_profile_fromstring, METH_VARARGS},
{"profile_fromstring", cms_profile_fromstring, METH_VARARGS},
{"profile_frombytes", cms_profile_frombytes, METH_VARARGS},
{"profile_tobytes", cms_profile_tobytes, METH_VARARGS},
/* profile and transform functions */