diff --git a/.appveyor.yml b/.appveyor.yml index de5e52742..41a5725b2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -53,9 +53,8 @@ test_script: - cd c:\pillow - '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml ipython numpy olefile pyroma' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% -- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"' -- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' -#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? +- path %PYTHON%;%PATH% +- .ci\test.cmd after_test: - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe diff --git a/.ci/test.cmd b/.ci/test.cmd new file mode 100644 index 000000000..aafc9290c --- /dev/null +++ b/.ci/test.cmd @@ -0,0 +1,3 @@ +python.exe -c "from PIL import Image" +IF ERRORLEVEL 1 EXIT /B +python.exe -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests diff --git a/.ci/test.sh b/.ci/test.sh index 8ff7c5f64..3f0ddc350 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -4,4 +4,4 @@ set -e python3 -c "from PIL import Image" -python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests $REVERSE +python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index e5e1ec32e..c7a73439c 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -80,8 +80,7 @@ jobs: - name: Test Pillow run: | python3 selftest.py --installed - python3 -c "from PIL import Image" - python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests + .ci/test.sh - name: Upload coverage uses: codecov/codecov-action@v4 diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 13147d86b..c8842e37b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -190,8 +190,8 @@ jobs: - name: Test Pillow run: | - path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH% - python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests + path %GITHUB_WORKSPACE%\winbuild\build\bin;%PATH% + .ci\test.cmd shell: cmd - name: Prepare to upload errors diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4d40f7fab..b3996d5a1 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -16,11 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.2 -if [[ "$MB_ML_VER" != 2014 ]]; then - HARFBUZZ_VERSION=9.0.0 -else - HARFBUZZ_VERSION=8.5.0 -fi +HARFBUZZ_VERSION=10.0.1 LIBPNG_VERSION=1.6.44 JPEGTURBO_VERSION=3.0.4 OPENJPEG_VERSION=2.5.2 @@ -65,21 +61,15 @@ function build_brotli { } function build_harfbuzz { - if [[ "$HARFBUZZ_VERSION" == 8.5.0 ]]; then - export FREETYPE_LIBS=-lfreetype - export FREETYPE_CFLAGS=-I/usr/local/include/freetype2/ - build_simple harfbuzz $HARFBUZZ_VERSION https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION tar.xz --with-freetype=yes --with-glib=no - export FREETYPE_LIBS="" - export FREETYPE_CFLAGS="" - else - local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) - (cd $out_dir \ - && meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled) - (cd $out_dir/build \ - && meson install) - if [[ "$MB_ML_LIBC" == "manylinux" ]]; then - cp /usr/local/lib64/libharfbuzz* /usr/local/lib - fi + python3 -m pip install meson ninja + + local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) + (cd $out_dir \ + && meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled) + (cd $out_dir/build \ + && meson install) + if [[ "$MB_ML_LIBC" == "manylinux" ]]; then + cp /usr/local/lib64/libharfbuzz* /usr/local/lib fi } @@ -155,13 +145,7 @@ if [[ -n "$IS_MACOS" ]]; then brew remove --ignore-dependencies webp fi - brew install meson pkg-config -elif [[ "$MB_ML_LIBC" == "manylinux" ]]; then - if [[ "$HARFBUZZ_VERSION" != 8.5.0 ]]; then - yum install -y meson - fi -else - apk add meson + brew install pkg-config fi wrap_wheel_builder build diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 11564c142..d3c2ac44c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -105,13 +105,18 @@ jobs: - name: "macOS 10.10 x86_64" os: macos-13 cibw_arch: x86_64 - build: "pp310* cp3{9,10,11}*" + build: "cp3{9,10,11}*" macosx_deployment_target: "10.10" - name: "macOS 10.13 x86_64" os: macos-13 cibw_arch: x86_64 build: "cp3{12,13}*" macosx_deployment_target: "10.13" + - name: "macOS 10.15 x86_64" + os: macos-13 + cibw_arch: x86_64 + build: "pp310*" + macosx_deployment_target: "10.15" - name: "macOS arm64" os: macos-latest cibw_arch: arm64 diff --git a/CHANGES.rst b/CHANGES.rst index 4ab9eaf2b..b4644c541 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,18 @@ Changelog (Pillow) 11.0.0 (unreleased) ------------------- +- Improved copying imagequant libraries #8420 + [radarhere] + +- Use Capsule for WebP saving #8386 + [homm, radarhere] + +- Fixed writing multiple StripOffsets to TIFF #8317 + [Yay295, radarhere] + +- Fix dereference before checking for NULL in ImagingTransformAffine #8398 + [PavlNekrasov] + - Use transposed size after opening for TIFF images #8390 [radarhere, homm] diff --git a/Makefile b/Makefile index 94f7565d8..ec4159627 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ lint-fix: python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black python3 -m black . python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff - python3 -m ruff --fix . + python3 -m ruff check --fix . .PHONY: mypy mypy: diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 190f83f40..44da25295 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -108,7 +108,8 @@ class TestFileTiff: assert_image_equal_tofile(im, "Tests/images/hopper.tif") with Image.open("Tests/images/hopper_bigtiff.tif") as im: - # multistrip support not yet implemented + # The data type of this file's StripOffsets tag is LONG8, + # which is not yet supported for offset data when saving multiple frames. del im.tag_v2[273] outfile = str(tmp_path / "temp.tif") diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 1e0310001..36aabf4f8 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -181,6 +181,29 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG +def test_save_multiple_stripoffsets() -> None: + ifd = TiffImagePlugin.ImageFileDirectory_v2() + ifd[TiffImagePlugin.STRIPOFFSETS] = (123, 456) + assert ifd.tagtype[TiffImagePlugin.STRIPOFFSETS] == TiffTags.LONG + + # all values are in little-endian + assert ifd.tobytes() == ( + # number of tags == 1 + b"\x01\x00" + # tag id (2 bytes), type (2 bytes), count (4 bytes), value (4 bytes) + # TiffImagePlugin.STRIPOFFSETS, TiffTags.LONG, 2, 18 + # where STRIPOFFSETS is 273, LONG is 4 + # and 18 is the offset of the tag data + b"\x11\x01\x04\x00\x02\x00\x00\x00\x12\x00\x00\x00" + # end of entries + b"\x00\x00\x00\x00" + # 26 is the total number of bytes output, + # the offset for any auxiliary strip data that will then be appended + # (123 + 26, 456 + 26) == (149, 482) + b"\x95\x00\x00\x00\xe2\x01\x00\x00" + ) + + def test_no_duplicate_50741_tag() -> None: assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["BestQualityScale"] == 50780 diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index e75e3ddd2..79f6bb4e0 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -72,7 +72,7 @@ class TestFileWebp: def _roundtrip( self, tmp_path: Path, mode: str, epsilon: float, args: dict[str, Any] = {} ) -> None: - temp_file = str(tmp_path / "temp.webp") + temp_file = tmp_path / "temp.webp" hopper(mode).save(temp_file, **args) with Image.open(temp_file) as image: @@ -116,7 +116,7 @@ class TestFileWebp: assert buffer_no_args.getbuffer() != buffer_method.getbuffer() def test_save_all(self, tmp_path: Path) -> None: - temp_file = str(tmp_path / "temp.webp") + temp_file = tmp_path / "temp.webp" im = Image.new("RGB", (1, 1)) im2 = Image.new("RGB", (1, 1), "#f00") im.save(temp_file, save_all=True, append_images=[im2]) @@ -127,6 +127,11 @@ class TestFileWebp: reloaded.seek(1) assert_image_similar(im2, reloaded, 1) + def test_unsupported_image_mode(self) -> None: + im = Image.new("1", (1, 1)) + with pytest.raises(ValueError): + _webp.WebPEncode(im.getim(), False, 0, 0, "", 4, 0, b"", "") + def test_icc_profile(self, tmp_path: Path) -> None: self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) self._roundtrip( @@ -151,18 +156,16 @@ class TestFileWebp: @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_message(self, tmp_path: Path) -> None: - temp_file = str(tmp_path / "temp.webp") im = Image.new("RGB", (15000, 15000)) with pytest.raises(ValueError) as e: - im.save(temp_file, method=0) + im.save(tmp_path / "temp.webp", method=0) assert str(e.value) == "encoding error 6" @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None: - temp_file = str(tmp_path / "temp.webp") im = Image.new("L", (16384, 16384)) with pytest.raises(ValueError) as e: - im.save(temp_file) + im.save(tmp_path / "temp.webp") assert ( str(e.value) == "encoding error 5: Image size exceeds WebP limit of 16383 pixels" @@ -187,9 +190,8 @@ class TestFileWebp: def test_no_resource_warning(self, tmp_path: Path) -> None: file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: - temp_file = str(tmp_path / "temp.webp") with warnings.catch_warnings(): - image.save(temp_file) + image.save(tmp_path / "temp.webp") def test_file_pointer_could_be_reused(self) -> None: file_path = "Tests/images/hopper.webp" @@ -204,15 +206,16 @@ class TestFileWebp: def test_invalid_background( self, background: int | tuple[int, ...], tmp_path: Path ) -> None: - temp_file = str(tmp_path / "temp.webp") + temp_file = tmp_path / "temp.webp" im = hopper() with pytest.raises(OSError): im.save(temp_file, save_all=True, append_images=[im], background=background) def test_background_from_gif(self, tmp_path: Path) -> None: + out_webp = tmp_path / "temp.webp" + # Save L mode GIF with background with Image.open("Tests/images/no_palette_with_background.gif") as im: - out_webp = str(tmp_path / "temp.webp") im.save(out_webp, save_all=True) # Save P mode GIF with background @@ -220,11 +223,10 @@ class TestFileWebp: original_value = im.convert("RGB").getpixel((1, 1)) # Save as WEBP - out_webp = str(tmp_path / "temp.webp") im.save(out_webp, save_all=True) # Save as GIF - out_gif = str(tmp_path / "temp.gif") + out_gif = tmp_path / "temp.gif" with Image.open(out_webp) as im: im.save(out_gif) @@ -234,10 +236,10 @@ class TestFileWebp: assert difference < 5 def test_duration(self, tmp_path: Path) -> None: + out_webp = tmp_path / "temp.webp" + with Image.open("Tests/images/dispose_bgnd.gif") as im: assert im.info["duration"] == 1000 - - out_webp = str(tmp_path / "temp.webp") im.save(out_webp, save_all=True) with Image.open(out_webp) as reloaded: @@ -245,7 +247,7 @@ class TestFileWebp: assert reloaded.info["duration"] == 1000 def test_roundtrip_rgba_palette(self, tmp_path: Path) -> None: - temp_file = str(tmp_path / "temp.webp") + temp_file = tmp_path / "temp.webp" im = Image.new("RGBA", (1, 1)).convert("P") assert im.mode == "P" assert im.palette is not None diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 867b31e4d..8d62d5ac7 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -23,14 +23,14 @@ else cargo cinstall --prefix=/usr --destdir=. # Copy into place - sudo cp usr/lib/libimagequant.so* /usr/lib/ + sudo find usr -name libimagequant.so* -exec cp {} /usr/lib/ \; sudo cp usr/include/libimagequant.h /usr/include/ if [ -n "$GITHUB_ACTIONS" ]; then # Copy to cache rm -rf ~/cache-$archive_name mkdir ~/cache-$archive_name - cp usr/lib/libimagequant.so* ~/cache-$archive_name/ + find usr -name libimagequant.so* -exec cp {} ~/cache-$archive_name/ \; cp usr/include/libimagequant.h ~/cache-$archive_name/ fi diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 070ba23a1..5d862403e 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=libraqm-0.10.1 +archive=libraqm-0.10.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/pyproject.toml b/pyproject.toml index 228c344e8..0d0a6f170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,9 +97,13 @@ config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" -[tool.ruff] -fix = true +[tool.black] +exclude = "wheels/multibuild" +[tool.ruff] +exclude = [ "wheels/multibuild" ] + +fix = true lint.select = [ "C4", # flake8-comprehensions "E", # pycodestyle errors diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 14e6ea278..d4c46a797 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -991,9 +991,11 @@ class ImageFileDirectory_v2(_IFDv2Base): if stripoffsets is not None: tag, typ, count, value, data = entries[stripoffsets] if data: - msg = "multistrip support not yet implemented" - raise NotImplementedError(msg) - value = self._pack("L", self._unpack("L", value)[0] + offset) + size, handler = self._load_dispatch[typ] + values = [val + offset for val in handler(self, data, self.legacy_api)] + data = self._write_dispatch[typ](self, *values) + else: + value = self._pack("L", self._unpack("L", value)[0] + offset) entries[stripoffsets] = tag, typ, count, value, data # pass 2: write entries to file diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index f8d6168ba..64188f28c 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -13,10 +13,6 @@ except ImportError: SUPPORTED = False -_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True} - -_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True} - _VP8_MODES_BY_IDENTIFIER = { b"VP8 ": "RGB", b"VP8X": "RGBA", @@ -153,6 +149,13 @@ class WebPImageFile(ImageFile.ImageFile): return self.__logical_frame +def _convert_frame(im: Image.Image) -> Image.Image: + # Make sure image mode is supported + if im.mode not in ("RGBX", "RGBA", "RGB"): + im = im.convert("RGBA" if im.has_transparency_data else "RGB") + return im + + def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: encoderinfo = im.encoderinfo.copy() append_images = list(encoderinfo.get("append_images", [])) @@ -243,31 +246,13 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: for idx in range(nfr): ims.seek(idx) - ims.load() - # Make sure image mode is supported - frame = ims - rawmode = ims.mode - if ims.mode not in _VALID_WEBP_MODES: - alpha = ( - "A" in ims.mode - or "a" in ims.mode - or (ims.mode == "P" and "A" in ims.im.getpalettemode()) - ) - rawmode = "RGBA" if alpha else "RGB" - frame = ims.convert(rawmode) - - if rawmode == "RGB": - # For faster conversion, use RGBX - rawmode = "RGBX" + frame = _convert_frame(ims) # Append the frame to the animation encoder enc.add( - frame.tobytes("raw", rawmode), + frame.getim(), round(timestamp), - frame.size[0], - frame.size[1], - rawmode, lossless, quality, alpha_quality, @@ -285,7 +270,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: im.seek(cur_idx) # Force encoder to flush frames - enc.add(None, round(timestamp), 0, 0, "", lossless, quality, alpha_quality, 0) + enc.add(None, round(timestamp), lossless, quality, alpha_quality, 0) # Get the final output from the encoder data = enc.assemble(icc_profile, exif, xmp) @@ -310,17 +295,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: method = im.encoderinfo.get("method", 4) exact = 1 if im.encoderinfo.get("exact") else 0 - if im.mode not in _VALID_WEBP_LEGACY_MODES: - im = im.convert("RGBA" if im.has_transparency_data else "RGB") + im = _convert_frame(im) data = _webp.WebPEncode( - im.tobytes(), - im.size[0], - im.size[1], + im.getim(), lossless, float(quality), float(alpha_quality), - im.mode, icc_profile, method, exact, diff --git a/src/_webp.c b/src/_webp.c index f59ad3036..dfda7048d 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -83,6 +83,49 @@ HandleMuxError(WebPMuxError err, char *chunk) { return NULL; } +/* -------------------------------------------------------------------- */ +/* Frame import */ +/* -------------------------------------------------------------------- */ + +static int +import_frame_libwebp(WebPPicture *frame, Imaging im) { + if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") && + strcmp(im->mode, "RGBX")) { + PyErr_SetString(PyExc_ValueError, "unsupported image mode"); + return -1; + } + + frame->width = im->xsize; + frame->height = im->ysize; + frame->use_argb = 1; // Don't convert RGB pixels to YUV + + if (!WebPPictureAlloc(frame)) { + PyErr_SetString(PyExc_MemoryError, "can't allocate picture frame"); + return -2; + } + + int ignore_fourth_channel = strcmp(im->mode, "RGBA"); + for (int y = 0; y < im->ysize; ++y) { + UINT8 *src = (UINT8 *)im->image32[y]; + UINT32 *dst = frame->argb + frame->argb_stride * y; + if (ignore_fourth_channel) { + for (int x = 0; x < im->xsize; ++x) { + dst[x] = + ((UINT32)(src[x * 4 + 2]) | ((UINT32)(src[x * 4 + 1]) << 8) | + ((UINT32)(src[x * 4]) << 16) | (0xff << 24)); + } + } else { + for (int x = 0; x < im->xsize; ++x) { + dst[x] = + ((UINT32)(src[x * 4 + 2]) | ((UINT32)(src[x * 4 + 1]) << 8) | + ((UINT32)(src[x * 4]) << 16) | ((UINT32)(src[x * 4 + 3]) << 24)); + } + } + } + + return 0; +} + /* -------------------------------------------------------------------- */ /* WebP Animation Support */ /* -------------------------------------------------------------------- */ @@ -180,16 +223,14 @@ _anim_encoder_dealloc(PyObject *self) { PyObject * _anim_encoder_add(PyObject *self, PyObject *args) { - uint8_t *rgb; - Py_ssize_t size; + PyObject *i0; + Imaging im; int timestamp; - int width; - int height; - char *mode; int lossless; float quality_factor; float alpha_quality_factor; int method; + ImagingSectionCookie cookie; WebPConfig config; WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self; WebPAnimEncoder *enc = encp->enc; @@ -197,13 +238,9 @@ _anim_encoder_add(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "z#iiisiffi", - (char **)&rgb, - &size, + "Oiiffi", + &i0, ×tamp, - &width, - &height, - &mode, &lossless, &quality_factor, &alpha_quality_factor, @@ -213,11 +250,18 @@ _anim_encoder_add(PyObject *self, PyObject *args) { } // Check for NULL frame, which sets duration of final frame - if (!rgb) { + if (i0 == Py_None) { WebPAnimEncoderAdd(enc, NULL, timestamp, NULL); Py_RETURN_NONE; } + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); + return NULL; + } + + im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + // Setup config for this frame if (!WebPConfigInit(&config)) { PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!"); @@ -234,20 +278,15 @@ _anim_encoder_add(PyObject *self, PyObject *args) { return NULL; } - // Populate the frame with raw bytes passed to us - frame->width = width; - frame->height = height; - frame->use_argb = 1; // Don't convert RGB pixels to YUV - if (strcmp(mode, "RGBA") == 0) { - WebPPictureImportRGBA(frame, rgb, 4 * width); - } else if (strcmp(mode, "RGBX") == 0) { - WebPPictureImportRGBX(frame, rgb, 4 * width); - } else { - WebPPictureImportRGB(frame, rgb, 3 * width); + if (import_frame_libwebp(frame, im)) { + return NULL; } - // Add the frame to the encoder - if (!WebPAnimEncoderAdd(enc, frame, timestamp, &config)) { + ImagingSectionEnter(&cookie); + int ok = WebPAnimEncoderAdd(enc, frame, timestamp, &config); + ImagingSectionLeave(&cookie); + + if (!ok) { PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc)); return NULL; } @@ -572,26 +611,21 @@ static PyTypeObject WebPAnimDecoder_Type = { PyObject * WebPEncode_wrapper(PyObject *self, PyObject *args) { - int width; - int height; int lossless; float quality_factor; float alpha_quality_factor; int method; int exact; - uint8_t *rgb; + Imaging im; + PyObject *i0; uint8_t *icc_bytes; uint8_t *exif_bytes; uint8_t *xmp_bytes; uint8_t *output; - char *mode; - Py_ssize_t size; Py_ssize_t icc_size; Py_ssize_t exif_size; Py_ssize_t xmp_size; size_t ret_size; - int rgba_mode; - int channels; int ok; ImagingSectionCookie cookie; WebPConfig config; @@ -600,15 +634,11 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "y#iiiffss#iis#s#", - (char **)&rgb, - &size, - &width, - &height, + "Oiffs#iis#s#", + &i0, &lossless, &quality_factor, &alpha_quality_factor, - &mode, &icc_bytes, &icc_size, &method, @@ -621,15 +651,12 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { return NULL; } - rgba_mode = strcmp(mode, "RGBA") == 0; - if (!rgba_mode && strcmp(mode, "RGB") != 0) { - Py_RETURN_NONE; + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); + return NULL; } - channels = rgba_mode ? 4 : 3; - if (size < width * height * channels) { - Py_RETURN_NONE; - } + im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); // Setup config for this frame if (!WebPConfigInit(&config)) { @@ -652,14 +679,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { PyErr_SetString(PyExc_ValueError, "could not initialise picture"); return NULL; } - pic.width = width; - pic.height = height; - pic.use_argb = 1; // Don't convert RGB pixels to YUV - if (rgba_mode) { - WebPPictureImportRGBA(&pic, rgb, channels * width); - } else { - WebPPictureImportRGB(&pic, rgb, channels * width); + if (import_frame_libwebp(&pic, im)) { + return NULL; } WebPMemoryWriterInit(&writer); diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index e2022d283..026d9d306 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -111,8 +111,8 @@ ARCHITECTURES = { V = { "BROTLI": "1.1.0", "FREETYPE": "2.13.3", - "FRIBIDI": "1.0.15", - "HARFBUZZ": "9.0.0", + "FRIBIDI": "1.0.16", + "HARFBUZZ": "10.0.1", "JPEGTURBO": "3.0.4", "LCMS2": "2.16", "LIBPNG": "1.6.44", @@ -292,8 +292,12 @@ DEPS: dict[str, dict[str, Any]] = { }, "build": [ cmd_rmdir("objs"), - cmd_msbuild("MSBuild.sln", "Release Static", "Clean"), - cmd_msbuild("MSBuild.sln", "Release Static", "Build"), + cmd_msbuild( + r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Clean" + ), + cmd_msbuild( + r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Build" + ), cmd_xcopy("include", "{inc_dir}"), ], "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],