diff --git a/.appveyor.yml b/.appveyor.yml index f86500b48..1cca224ab 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -25,8 +25,8 @@ install: - mv c:\pillow-depends-main c:\pillow-depends - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ -- ..\pillow-depends\gs9550w32.exe /S -- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.55.0\bin;%PATH% +- ..\pillow-depends\gs9561w32.exe /S +- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ @@ -43,7 +43,7 @@ build_script: test_script: - cd c:\pillow -- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov' +- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout' - 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' diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 7e2fbf28f..0e0abaf95 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -31,13 +31,13 @@ jobs: language: python dry-run: false - name: Upload New Crash - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts - name: Upload Legacy Crash - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: steps.run.outcome == 'success' with: name: crash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 533ce8cbd..4540fb5af 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: name: Lint steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: pre-commit cache uses: actions/cache@v2 @@ -21,7 +21,7 @@ jobs: lint-pre-commit- - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: "3.10" cache: pip diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..cc5e0d488 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: Close stale issues + +on: + schedule: + - cron: "10 0 * * *" + workflow_dispatch: + +permissions: + issues: write + +jobs: + stale: + if: github.repository_owner == 'python-pillow' + + runs-on: ubuntu-latest + + steps: + - name: "Check issues" + uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + only-labels: "Awaiting OP Action" + close-issue-message: "Closing this issue as no feedback has been received." + days-before-stale: 7 + days-before-issue-close: 0 + days-before-pr-close: -1 + labels-to-remove-when-unstale: "Awaiting OP Action" diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 2762d80c9..fc4667387 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -23,7 +23,6 @@ jobs: centos-stream-9-amd64, debian-10-buster-x86, debian-11-bullseye-x86, - fedora-34-amd64, fedora-35-amd64, gentoo, ubuntu-18.04-bionic-amd64, @@ -41,7 +40,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 51bd3a300..7b5cc8a97 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up shell run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 4a8966ca8..21a2b469e 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -28,7 +28,7 @@ jobs: name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e2cf44cae..6ed8bb0c5 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -23,17 +23,17 @@ jobs: steps: - name: Checkout Pillow - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout cached dependencies - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: python-pillow/pillow-depends path: winbuild\depends # sets env: pythonLocation - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -52,8 +52,8 @@ jobs: 7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH - winbuild\depends\gs9550w32.exe /S - echo "C:\Program Files (x86)\gs\gs9.55.0\bin" >> $env:GITHUB_PATH + winbuild\depends\gs9561w32.exe /S + echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH xcopy /S /Y winbuild\depends\test_images\* Tests\images\ @@ -156,7 +156,7 @@ jobs: shell: bash - name: Upload errors - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() with: name: errors @@ -182,7 +182,7 @@ jobs: winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: "github.event_name != 'pull_request'" with: name: ${{ steps.wheel.outputs.dist }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 133972881..7b13addfd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,10 +36,10 @@ jobs: name: ${{ matrix.os }} Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} cache: pip @@ -84,7 +84,7 @@ jobs: mkdir -p Tests/errors - name: Upload errors - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() with: name: errors @@ -93,7 +93,7 @@ jobs: - name: Docs if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10 run: | - python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph + python3 -m pip install furo sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph make doccheck - name: After success diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index c2b8b3bda..2e8c9b730 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Scan uses: tidelift/alignment-action@main env: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 822fa43ca..353dd0c19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: f1d4e742c91dd5179d742b0db9293c4472b765f8 # frozen: 21.12b0 + rev: 22.3.0 hooks: - id: black args: ["--target-version", "py37"] @@ -9,35 +9,35 @@ repos: types: [] - repo: https://github.com/PyCQA/isort - rev: c5e8fa75dda5f764d20f66a215d71c21cfa198e1 # frozen: 5.10.1 + rev: 5.10.1 hooks: - id: isort - repo: https://github.com/asottile/yesqa - rev: 35cf7dc24fa922927caded7a21b2a8cb04bf8e10 # frozen: v1.3.0 + rev: v1.3.0 hooks: - id: yesqa - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10 + rev: v1.1.13 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - repo: https://github.com/PyCQA/flake8 - rev: cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d # frozen: 4.0.1 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - repo: https://github.com/pre-commit/pygrep-hooks - rev: 6f51a66bba59954917140ec2eeeaa4d5e630e6ce # frozen: v1.9.0 + rev: v1.9.0 hooks: - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 8fe62d14e0b4d7d845a7022c5c2c3ae41bdd3f26 # frozen: v4.1.0 + rev: v4.1.0 hooks: - id: check-merge-conflict - id: check-yaml diff --git a/CHANGES.rst b/CHANGES.rst index 904a61ce1..dc69e4587 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,9 +2,90 @@ Changelog (Pillow) ================== -9.1.0 (unreleased) +9.2.0 (unreleased) ------------------ +- Round lut values where necessary #6188 + [radarhere] + +- Load before getting size in resize() #6190 + [radarhere] + +- Load image before performing size calculations in thumbnail() #6186 + [radarhere] + +- Deprecated PhotoImage.paste() box parameter #6178 + [radarhere] + +9.1.0 (2022-04-01) +------------------ + +- Add support for multiple component transformation to JPEG2000 #5500 + [scaramallion, radarhere, hugovk] + +- Fix loading FriBiDi on Alpine #6165 + [nulano] + +- Added setting for converting GIF P frames to RGB #6150 + [radarhere] + +- Allow 1 mode images to be inverted #6034 + [radarhere] + +- Raise ValueError when trying to save empty JPEG #6159 + [radarhere] + +- Always save TIFF with contiguous planar configuration #5973 + [radarhere] + +- Connected discontiguous polygon corners #5980 + [radarhere] + +- Ensure Tkinter hook is activated for getimage() #6032 + [radarhere] + +- Use screencapture arguments to crop on macOS #6152 + [radarhere] + +- Do not mark L mode JPEG as 1 bit in PDF #6151 + [radarhere] + +- Added support for reading I;16R TIFF images #6132 + [radarhere] + +- If an error occurs after creating a file, remove the file #6134 + [radarhere] + +- Fixed calling DisplayViewer or XVViewer without a title #6136 + [radarhere] + +- Retain RGBA transparency when saving multiple GIF frames #6128 + [radarhere] + +- Save additional ICO frames with other bit depths if supplied #6122 + [radarhere] + +- Handle EXIF data truncated to just the header #6124 + [radarhere] + +- Added support for reading BMP images with RLE8 compression #6102 + [radarhere] + +- Support Python distributions where _tkinter is compiled in #6006 + [lukegb] + +- Added support for PPM arbitrary maxval #6119 + [radarhere] + +- Added BigTIFF reading #6097 + [radarhere] + +- When converting, clip I;16 to be unsigned, not signed #6112 + [radarhere] + +- Fixed loading L mode GIF with transparency #6086 + [radarhere] + - Improved handling of PPM header #5121 [Piolie, radarhere] diff --git a/Makefile b/Makefile index 263598599..437050ed4 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ release-test: -rm dist/*.egg -rmdir dist python3 -m pytest -qq - python3 -m check-manifest + python3 -m check_manifest python3 -m pyroma . $(MAKE) readme diff --git a/RELEASING.md b/RELEASING.md index cbedd449c..a6049b685 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -24,13 +24,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. * [ ] Create and check source distribution: ```bash make sdist - twine check dist/* + python3 -m twine check --strict dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Check and upload all binaries and source distributions e.g.: ```bash - twine check dist/* - twine upload dist/Pillow-5.2.0* + python3 -m twine check --strict dist/* + python3 -m twine upload dist/Pillow-5.2.0* ``` * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` @@ -61,13 +61,13 @@ Released as needed for security, installation or critical bug fixes. * [ ] Create and check source distribution: ```bash make sdist - twine check dist/* + python3 -m twine check --strict dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Check and upload all binaries and source distributions e.g.: ```bash - twine check dist/* - twine upload dist/Pillow-5.2.1* + python3 -m twine check --strict dist/* + python3 -m twine upload dist/Pillow-5.2.1* ``` * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) @@ -91,7 +91,7 @@ Released as needed privately to individual vendors for critical security-related * [ ] Create and check source distribution: ```bash make sdist - twine check dist/* + python3 -m twine check --strict dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) diff --git a/Tests/32bit_segfault_check.py b/Tests/32bit_segfault_check.py index e19cdf7a9..2ff7f908f 100755 --- a/Tests/32bit_segfault_check.py +++ b/Tests/32bit_segfault_check.py @@ -4,5 +4,5 @@ import sys from PIL import Image -if sys.maxsize < 2 ** 32: +if sys.maxsize < 2**32: im = Image.new("L", (999999, 999999), 0) diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index c191ffc1e..d98f4a694 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -23,7 +23,7 @@ YDIM = 32769 XDIM = 48000 -pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") +pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system") def _write_png(tmp_path, xdim, ydim): diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index 70ae6d230..24cb1f722 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -19,7 +19,7 @@ YDIM = 32769 XDIM = 48000 -pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") +pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system") def _write_png(tmp_path, xdim, ydim): diff --git a/Tests/images/16bit.r.tif b/Tests/images/16bit.r.tif new file mode 100644 index 000000000..0f3996e95 Binary files /dev/null and b/Tests/images/16bit.r.tif differ diff --git a/Tests/images/hopper_bigtiff.tif b/Tests/images/hopper_bigtiff.tif new file mode 100644 index 000000000..9588a37d8 Binary files /dev/null and b/Tests/images/hopper_bigtiff.tif differ diff --git a/Tests/images/hopper_rle8.bmp b/Tests/images/hopper_rle8.bmp new file mode 100644 index 000000000..0fff4a0d4 Binary files /dev/null and b/Tests/images/hopper_rle8.bmp differ diff --git a/Tests/images/hopper_rle8_row_overflow.bmp b/Tests/images/hopper_rle8_row_overflow.bmp new file mode 100644 index 000000000..d606dc3e4 Binary files /dev/null and b/Tests/images/hopper_rle8_row_overflow.bmp differ diff --git a/Tests/images/imagedraw/discontiguous_corners_polygon.png b/Tests/images/imagedraw/discontiguous_corners_polygon.png new file mode 100644 index 000000000..509c42b26 Binary files /dev/null and b/Tests/images/imagedraw/discontiguous_corners_polygon.png differ diff --git a/Tests/images/no_palette.gif b/Tests/images/no_palette.gif new file mode 100644 index 000000000..0432ebcb6 Binary files /dev/null and b/Tests/images/no_palette.gif differ diff --git a/Tests/images/no_palette_with_background.gif b/Tests/images/no_palette_with_background.gif new file mode 100644 index 000000000..e49e5d461 Binary files /dev/null and b/Tests/images/no_palette_with_background.gif differ diff --git a/Tests/images/no_palette_with_transparency.gif b/Tests/images/no_palette_with_transparency.gif new file mode 100644 index 000000000..031bdcfce Binary files /dev/null and b/Tests/images/no_palette_with_transparency.gif differ diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 440bc325b..b17aad2ea 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -40,6 +40,7 @@ def test_questionable(): "rgb32fakealpha.bmp", "rgb24largepal.bmp", "pal8os2sp.bmp", + "pal8rletrns.bmp", "rgb32bf-xbgr.bmp", ] for f in get_files("q"): diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 6c52d25a4..385192a3c 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -110,9 +110,9 @@ class TestCoreMemory: with pytest.raises(ValueError): Image.core.set_blocks_max(-1) - if sys.maxsize < 2 ** 32: + if sys.maxsize < 2**32: with pytest.raises(ValueError): - Image.core.set_blocks_max(2 ** 29) + Image.core.set_blocks_max(2**29) @pytest.mark.skipif(is_pypy(), reason="Images not collected") def test_set_blocks_max_stats(self): diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 47fc97df0..f214fd6bd 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -4,7 +4,12 @@ import pytest from PIL import BmpImagePlugin, Image -from .helper import assert_image_equal, assert_image_equal_tofile, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar_tofile, + hopper, +) def test_sanity(tmp_path): @@ -125,6 +130,42 @@ def test_rgba_bitfields(): assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") +def test_rle8(): + with Image.open("Tests/images/hopper_rle8.bmp") as im: + assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) + + # This test image has been manually hexedited + # to have rows with too much data + with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im: + assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) + + # Signal end of bitmap before the image is finished + with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp: + data = fp.read(1063) + b"\x01" + with Image.open(io.BytesIO(data)) as im: + with pytest.raises(ValueError): + im.load() + + +@pytest.mark.parametrize( + "file_name,length", + ( + # EOF immediately after the header + ("Tests/images/hopper_rle8.bmp", 1078), + # EOF during delta + ("Tests/images/bmp/q/pal8rletrns.bmp", 3670), + # EOF when reading data in absolute mode + ("Tests/images/bmp/g/pal8rle.bmp", 1064), + ), +) +def test_rle8_eof(file_name, length): + with open(file_name, "rb") as fp: + data = fp.read(length) + with Image.open(io.BytesIO(data)) as im: + with pytest.raises(ValueError): + im.load() + + def test_offset(): # This image has been hexedited # to exclude the palette size from the pixel data offset diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 2f46ed77e..58447122e 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -196,6 +196,13 @@ def test__accept_false(): assert not output +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + DdsImagePlugin.DdsImageFile(invalid_file) + + def test_short_header(): """Check a short header""" with open(TEST_FILE_DXT5, "rb") as f: diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 5447dc740..cae20fa46 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -16,6 +16,13 @@ def test_load_dxt1(): assert_image_similar(im, target.convert("RGBA"), 15) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + FtexImagePlugin.FtexImageFile(invalid_file) + + def test_constants_deprecation(): for enum, prefix in { FtexImagePlugin.Format: "FORMAT_", diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 011d982f0..dffd1006f 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -59,6 +59,51 @@ def test_invalid_file(): GifImagePlugin.GifImageFile(invalid_file) +def test_l_mode_transparency(): + with Image.open("Tests/images/no_palette_with_transparency.gif") as im: + assert im.mode == "L" + assert im.load()[0, 0] == 128 + assert im.info["transparency"] == 255 + + im.seek(1) + assert im.mode == "L" + assert im.load()[0, 0] == 128 + + +def test_strategy(): + with Image.open("Tests/images/chi.gif") as im: + expected_zero = im.convert("RGB") + + im.seek(1) + expected_one = im.convert("RGB") + + try: + GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS + with Image.open("Tests/images/chi.gif") as im: + assert im.mode == "RGB" + assert_image_equal(im, expected_zero) + + GifImagePlugin.LOADING_STRATEGY = ( + GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY + ) + # Stay in P mode with only a global palette + with Image.open("Tests/images/chi.gif") as im: + assert im.mode == "P" + + im.seek(1) + assert im.mode == "P" + assert_image_equal(im.convert("RGB"), expected_one) + + # Change to RGB mode when a frame has an individual palette + with Image.open("Tests/images/iss634.gif") as im: + assert im.mode == "P" + + im.seek(1) + assert im.mode == "RGB" + finally: + GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST + + def test_optimize(): def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) @@ -383,18 +428,38 @@ def test_dispose_background_transparency(): assert px[35, 30][3] == 0 -def test_transparent_dispose(): - expected_colors = [ - (2, 1, 2), - ((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)), - ((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)), - ] - with Image.open("Tests/images/transparent_dispose.gif") as img: - for frame in range(3): - img.seek(frame) - for x in range(3): - color = img.getpixel((x, 0)) - assert color == expected_colors[frame][x] +@pytest.mark.parametrize( + "loading_strategy, expected_colors", + ( + ( + GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST, + ( + (2, 1, 2), + ((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)), + ((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)), + ), + ), + ( + GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY, + ( + (2, 1, 2), + (0, 1, 0), + (2, 1, 2), + ), + ), + ), +) +def test_transparent_dispose(loading_strategy, expected_colors): + GifImagePlugin.LOADING_STRATEGY = loading_strategy + try: + with Image.open("Tests/images/transparent_dispose.gif") as img: + for frame in range(3): + img.seek(frame) + for x in range(3): + color = img.getpixel((x, 0)) + assert color == expected_colors[frame][x] + finally: + GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST def test_dispose_previous(): @@ -831,6 +896,17 @@ def test_rgb_transparency(tmp_path): assert "transparency" not in reloaded.info +def test_rgba_transparency(tmp_path): + out = str(tmp_path / "temp.gif") + + im = hopper("P") + im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)]) + + with Image.open(out) as reloaded: + reloaded.seek(1) + assert_image_equal(hopper("P").convert("RGB"), reloaded) + + def test_bbox(tmp_path): out = str(tmp_path / "temp.gif") @@ -960,6 +1036,11 @@ def test_lzw_bits(): def test_extents(): with Image.open("Tests/images/test_extents.gif") as im: assert im.size == (100, 100) + + # Check that n_frames does not change the size + assert im.n_frames == 2 + assert im.size == (100, 100) + im.seek(1) assert im.size == (150, 150) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 12b80fbde..3fcd5c61f 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,4 +1,5 @@ import io +import os import pytest @@ -70,6 +71,53 @@ def test_save_to_bytes(): ) +def test_no_duplicates(tmp_path): + temp_file = str(tmp_path / "temp.ico") + temp_file2 = str(tmp_path / "temp2.ico") + + im = hopper() + sizes = [(32, 32), (64, 64)] + im.save(temp_file, "ico", sizes=sizes) + + sizes.append(sizes[-1]) + im.save(temp_file2, "ico", sizes=sizes) + + assert os.path.getsize(temp_file) == os.path.getsize(temp_file2) + + +def test_different_bit_depths(tmp_path): + temp_file = str(tmp_path / "temp.ico") + temp_file2 = str(tmp_path / "temp2.ico") + + im = hopper() + im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)]) + + hopper("1").save( + temp_file2, + "ico", + bitmap_format="bmp", + sizes=[(128, 128)], + append_images=[im], + ) + + assert os.path.getsize(temp_file) != os.path.getsize(temp_file2) + + # Test that only matching sizes of different bit depths are saved + temp_file3 = str(tmp_path / "temp3.ico") + temp_file4 = str(tmp_path / "temp4.ico") + + im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)]) + im.save( + temp_file4, + "ico", + bitmap_format="bmp", + sizes=[(128, 128)], + append_images=[Image.new("P", (64, 64))], + ) + + assert os.path.getsize(temp_file3) == os.path.getsize(temp_file4) + + @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA")) def test_save_to_bytes_bmp(mode): output = io.BytesIO() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index e5e8c85f4..203065802 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -68,6 +68,13 @@ class TestFileJpeg: assert im.format == "JPEG" assert im.get_format_mimetype() == "image/jpeg" + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) + def test_zero(self, size, tmp_path): + f = str(tmp_path / "temp.jpg") + im = Image.new("RGB", size) + with pytest.raises(ValueError): + im.save(f) + def test_app(self): # Test APP/COM reader (@PIL135) with Image.open(TEST_FILE) as im: diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b5ea6d0a0..677a14981 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -209,6 +209,49 @@ def test_layers(): assert_image_similar(im, test_card, 0.4) +@pytest.mark.parametrize( + "name, args, offset, data", + ( + ("foo.j2k", {}, 0, b"\xff\x4f"), + ("foo.jp2", {}, 4, b"jP"), + (None, {"no_jp2": True}, 0, b"\xff\x4f"), + ("foo.j2k", {"no_jp2": True}, 0, b"\xff\x4f"), + ("foo.jp2", {"no_jp2": True}, 0, b"\xff\x4f"), + ("foo.j2k", {"no_jp2": False}, 0, b"\xff\x4f"), + ("foo.jp2", {"no_jp2": False}, 4, b"jP"), + ("foo.jp2", {"no_jp2": False}, 4, b"jP"), + ), +) +def test_no_jp2(name, args, offset, data): + out = BytesIO() + if name: + out.name = name + test_card.save(out, "JPEG2000", **args) + out.seek(offset) + assert out.read(2) == data + + +def test_mct(): + # Three component + for val in (0, 1): + out = BytesIO() + test_card.save(out, "JPEG2000", mct=val, no_jp2=True) + + assert out.getvalue()[59] == val + with Image.open(out) as im: + assert_image_similar(im, test_card, 1.0e-3) + + # Single component should have MCT disabled + for val in (0, 1): + out = BytesIO() + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + jp2.save(out, "JPEG2000", mct=val, no_jp2=True) + + assert out.getvalue()[53] == 0 + with Image.open(out) as im: + assert_image_similar(im, jp2, 1.0e-3) + + def test_rgba(): # Arrange with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 53ed2520a..d83c584b5 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -4,7 +4,6 @@ import itertools import os import re from collections import namedtuple -from ctypes import c_float import pytest @@ -168,14 +167,11 @@ class TestFileLibTiff(LibTiffTestCase): val = original[tag] if tag.endswith("Resolution"): if legacy_api: - assert ( - c_float(val[0][0] / val[0][1]).value - == c_float(value[0][0] / value[0][1]).value + assert val[0][0] / val[0][1] == ( + 4294967295 / 113653537 ), f"{tag} didn't roundtrip" else: - assert ( - c_float(val).value == c_float(value).value - ), f"{tag} didn't roundtrip" + assert val == 37.79000115940079, f"{tag} didn't roundtrip" else: assert val == value, f"{tag} didn't roundtrip" @@ -218,7 +214,7 @@ class TestFileLibTiff(LibTiffTestCase): values = { 2: "test", 3: 1, - 4: 2 ** 20, + 4: 2**20, 5: TiffImagePlugin.IFDRational(100, 1), 12: 1.05, } @@ -1019,7 +1015,7 @@ class TestFileLibTiff(LibTiffTestCase): im = hopper("RGB").resize((256, 256)) out = str(tmp_path / "temp.tif") - TiffImagePlugin.STRIP_SIZE = 2 ** 18 + TiffImagePlugin.STRIP_SIZE = 2**18 try: im.save(out, compression="tiff_adobe_deflate") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 0e4f1ba68..2c965318b 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -13,16 +13,53 @@ TEST_FILE = "Tests/images/hopper.ppm" def test_sanity(): with Image.open(TEST_FILE) as im: - im.load() assert im.mode == "RGB" assert im.size == (128, 128) - assert im.format, "PPM" + assert im.format == "PPM" assert im.get_format_mimetype() == "image/x-portable-pixmap" +@pytest.mark.parametrize( + "data, mode, pixels", + ( + (b"P5 3 1 4 \x00\x02\x04", "L", (0, 128, 255)), + (b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)), + # P6 with maxval < 255 + ( + b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11", + "RGB", + ( + (0, 15, 30), + (120, 135, 150), + (225, 240, 255), + ), + ), + # P6 with maxval > 255 + # Scale down to 255, since there is no RGB mode with more than 8-bit + ( + b"P6 3 1 257 \x00\x00\x00\x01\x00\x02" + b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF", + "RGB", + ( + (0, 1, 2), + (127, 128, 129), + (254, 255, 255), + ), + ), + ), +) +def test_arbitrary_maxval(data, mode, pixels): + fp = BytesIO(data) + with Image.open(fp) as im: + assert im.size == (3, 1) + assert im.mode == mode + + px = im.load() + assert tuple(px[x, 0] for x in range(3)) == pixels + + def test_16bit_pgm(): with Image.open("Tests/images/16_bit_binary.pgm") as im: - im.load() assert im.mode == "I" assert im.size == (20, 100) assert im.get_format_mimetype() == "image/x-portable-graymap" @@ -32,8 +69,6 @@ def test_16bit_pgm(): def test_16bit_pgm_write(tmp_path): with Image.open("Tests/images/16_bit_binary.pgm") as im: - im.load() - f = str(tmp_path / "temp.pgm") im.save(f, "PPM") @@ -91,19 +126,8 @@ def test_token_too_long(tmp_path): assert str(e.value) == "Token too long in file header: b'01234567890'" -def test_too_many_colors(tmp_path): - path = str(tmp_path / "temp.ppm") - with open(path, "wb") as f: - f.write(b"P6\n1 1\n1000\n") - - with pytest.raises(ValueError) as e: - with Image.open(path): - pass - - assert str(e.value) == "Too many colors for band: 1000" - - def test_truncated_file(tmp_path): + # Test EOF in header path = str(tmp_path / "temp.pgm") with open(path, "w") as f: f.write("P6") @@ -114,6 +138,12 @@ def test_truncated_file(tmp_path): assert str(e.value) == "Reached EOF while reading header" + # Test EOF for PyDecoder + fp = BytesIO(b"P5 3 1 4") + with Image.open(fp) as im: + with pytest.raises(ValueError): + im.load() + def test_neg_ppm(): # Storage.c accepted negative values for xsize, ysize. the diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 28aeff075..c53bb87e8 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -87,6 +87,10 @@ class TestFileTiff: assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) + def test_bigtiff(self): + with Image.open("Tests/images/hopper_bigtiff.tif") as im: + assert_image_equal_tofile(im, "Tests/images/hopper.tif") + @pytest.mark.parametrize( "file_name,mode,size,offset", [ @@ -221,6 +225,15 @@ class TestFileTiff: assert b[0] == ord(b"\x01") assert b[1] == ord(b"\xe0") + def test_16bit_r(self): + with Image.open("Tests/images/16bit.r.tif") as im: + assert im.getpixel((0, 0)) == 480 + assert im.mode == "I;16" + + b = im.tobytes() + assert b[0] == ord(b"\xe0") + assert b[1] == ord(b"\x01") + def test_16bit_s(self): with Image.open("Tests/images/16bit.s.tif") as im: im.load() @@ -598,6 +611,17 @@ class TestFileTiff: with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + def test_planar_configuration_save(self, tmp_path): + infile = "Tests/images/tiff_tiled_planar_raw.tif" + with Image.open(infile) as im: + assert im._planar_configuration == 2 + + outfile = str(tmp_path / "temp.tif") + im.save(outfile) + + with Image.open(outfile) as reloaded: + assert_image_equal_tofile(reloaded, infile) + def test_palette(self, tmp_path): def roundtrip(mode): outfile = str(tmp_path / "temp.tif") diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 2213af5aa..056295516 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -258,7 +258,7 @@ def test_ifd_unsigned_rational(tmp_path): im = hopper() info = TiffImagePlugin.ImageFileDirectory_v2() - max_long = 2 ** 32 - 1 + max_long = 2**32 - 1 # 4 bytes unsigned long numerator = max_long @@ -290,8 +290,8 @@ def test_ifd_signed_rational(tmp_path): info = TiffImagePlugin.ImageFileDirectory_v2() # pair of 4 byte signed longs - numerator = 2 ** 31 - 1 - denominator = -(2 ** 31) + numerator = 2**31 - 1 + denominator = -(2**31) info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) @@ -302,8 +302,8 @@ def test_ifd_signed_rational(tmp_path): assert numerator == reloaded.tag_v2[37380].numerator assert denominator == reloaded.tag_v2[37380].denominator - numerator = -(2 ** 31) - denominator = 2 ** 31 - 1 + numerator = -(2**31) + denominator = 2**31 - 1 info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) @@ -315,7 +315,7 @@ def test_ifd_signed_rational(tmp_path): assert denominator == reloaded.tag_v2[37380].denominator # out of bounds of 4 byte signed long - numerator = -(2 ** 31) - 1 + numerator = -(2**31) - 1 denominator = 1 info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) @@ -324,7 +324,7 @@ def test_ifd_signed_rational(tmp_path): im.save(out, tiffinfo=info, compression="raw") with Image.open(out) as reloaded: - assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator + assert 2**31 - 1 == reloaded.tag_v2[37380].numerator assert -1 == reloaded.tag_v2[37380].denominator diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 55897f1eb..c69e13a89 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -8,6 +8,7 @@ import pytest from PIL import Image, WebPImagePlugin, features from .helper import ( + assert_image_equal, assert_image_similar, assert_image_similar_tofile, hopper, @@ -105,6 +106,19 @@ class TestFileWebp: hopper().save(buffer_method, format="WEBP", method=6) assert buffer_no_args.getbuffer() != buffer_method.getbuffer() + @skip_unless_feature("webp_anim") + def test_save_all(self, tmp_path): + temp_file = str(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]) + + with Image.open(temp_file) as reloaded: + assert_image_equal(im, reloaded) + + reloaded.seek(1) + assert_image_similar(im2, reloaded, 1) + def test_icc_profile(self, tmp_path): self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) if _webp.HAVE_WEBPANIM: @@ -128,7 +142,7 @@ class TestFileWebp: self._roundtrip(tmp_path, "P", 50.0) - @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") + @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_message(self, tmp_path): temp_file = str(tmp_path / "temp.webp") im = Image.new("RGB", (15000, 15000)) @@ -171,9 +185,14 @@ class TestFileWebp: Image.open(blob).load() Image.open(blob).load() - @skip_unless_feature("webp") @skip_unless_feature("webp_anim") def test_background_from_gif(self, tmp_path): + # 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 with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) @@ -191,7 +210,6 @@ class TestFileWebp: difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) assert difference < 5 - @skip_unless_feature("webp") @skip_unless_feature("webp_anim") def test_duration(self, tmp_path): with Image.open("Tests/images/dispose_bgnd.gif") as im: diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 25ebffe02..8606f6aaf 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,6 +1,7 @@ import pytest +from packaging.version import parse as parse_version -from PIL import Image +from PIL import Image, features from .helper import ( assert_image_equal, @@ -27,7 +28,6 @@ def test_n_frames(): assert im.is_animated -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_write_animation_L(tmp_path): """ Convert an animated GIF to animated WebP, then compare the frame count, and first @@ -46,6 +46,11 @@ def test_write_animation_L(tmp_path): orig.load() im.load() assert_image_similar(im, orig.convert("RGBA"), 32.9) + + if is_big_endian(): + webp = parse_version(features.version_module("webp")) + if webp < parse_version("1.2.2"): + pytest.skip("Fails with libwebp earlier than 1.2.2") orig.seek(orig.n_frames - 1) im.seek(im.n_frames - 1) orig.load() @@ -53,7 +58,6 @@ def test_write_animation_L(tmp_path): assert_image_similar(im, orig.convert("RGBA"), 32.9) -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_write_animation_RGB(tmp_path): """ Write an animated WebP from RGB frames, and ensure the frames @@ -69,6 +73,10 @@ def test_write_animation_RGB(tmp_path): assert_image_equal(im, frame1.convert("RGBA")) # Compare second frame to original + if is_big_endian(): + webp = parse_version(features.version_module("webp")) + if webp < parse_version("1.2.2"): + pytest.skip("Fails with libwebp earlier than 1.2.2") im.seek(1) im.load() assert_image_equal(im, frame2.convert("RGBA")) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 487920a92..9c54c6755 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -2,7 +2,7 @@ from io import BytesIO import pytest -from PIL import Image +from PIL import Image, XbmImagePlugin from .helper import hopper @@ -63,6 +63,13 @@ def test_open_filename_with_underscore(): assert im.size == (128, 128) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + XbmImagePlugin.XbmImageFile(invalid_file) + + def test_save_wrong_mode(tmp_path): im = hopper() out = str(tmp_path / "temp.xbm") diff --git a/Tests/test_image.py b/Tests/test_image.py index 2cd858df1..07cf6eb92 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -652,6 +652,15 @@ class TestImage: with warnings.catch_warnings(): im.save(temp_file) + def test_no_new_file_on_error(self, tmp_path): + temp_file = str(tmp_path / "temp.jpg") + + im = Image.new("RGB", (0, 0)) + with pytest.raises(ValueError): + im.save(temp_file) + + assert not os.path.exists(temp_file) + def test_load_on_nonexclusive_multiframe(self): with open("Tests/images/frozenpond.mpo", "rb") as fp: @@ -666,6 +675,19 @@ class TestImage: assert not fp.closed + def test_empty_exif(self): + with Image.open("Tests/images/exif.png") as im: + exif = im.getexif() + assert dict(exif) != {} + + # Test that exif data is cleared after another load + exif.load(None) + assert dict(exif) == {} + + # Test loading just the EXIF header + exif.load(b"Exif\x00\x00") + assert dict(exif) == {} + @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 7b3036979..617274a57 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -1,4 +1,3 @@ -import ctypes import os import subprocess import sys @@ -154,14 +153,17 @@ class TestImageGetPixel(AccessTest): # Check 0 im = Image.new(mode, (0, 0), None) - with pytest.raises(IndexError): + assert im.load() is not None + + error = ValueError if self._need_cffi_access else IndexError + with pytest.raises(error): im.putpixel((0, 0), c) - with pytest.raises(IndexError): + with pytest.raises(error): im.getpixel((0, 0)) # Check 0 negative index - with pytest.raises(IndexError): + with pytest.raises(error): im.putpixel((-1, -1), c) - with pytest.raises(IndexError): + with pytest.raises(error): im.getpixel((-1, -1)) # check initial color @@ -176,10 +178,10 @@ class TestImageGetPixel(AccessTest): # Check 0 im = Image.new(mode, (0, 0), c) - with pytest.raises(IndexError): + with pytest.raises(error): im.getpixel((0, 0)) # Check 0 negative index - with pytest.raises(IndexError): + with pytest.raises(error): im.getpixel((-1, -1)) def test_basic(self): @@ -205,10 +207,10 @@ class TestImageGetPixel(AccessTest): # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* for mode in ("I;16", "I;16B"): - 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, 2**15 - 1) + self.check(mode, 2**15) + self.check(mode, 2**15 + 1) + self.check(mode, 2**16 - 1) def test_p_putpixel_rgb_rgba(self): for color in [(255, 0, 0), (255, 0, 0, 255)]: @@ -386,7 +388,7 @@ class TestImagePutPixelError(AccessTest): def test_putpixel_overflow_error(self, mode): im = hopper(mode) with pytest.raises(OverflowError): - im.putpixel((0, 0), 2 ** 80) + im.putpixel((0, 0), 2**80) def test_putpixel_unrecognized_mode(self): im = hopper("BGR;15") @@ -401,6 +403,8 @@ class TestEmbeddable: "not from shell", ) def test_embeddable(self): + import ctypes + with open("embed_pil.c", "w") as fh: fh.write( """ diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1d6469819..727c282d7 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -70,6 +70,11 @@ def test_16bit(): with Image.open("Tests/images/16bit.cropped.tif") as im: _test_float_conversion(im) + for color in (65535, 65536): + im = Image.new("I", (1, 1), color) + im_i16 = im.convert("I;16") + assert im_i16.getpixel((0, 0)) == 65535 + def test_16bit_workaround(): with Image.open("Tests/images/16bit.cropped.tif") as im: @@ -135,6 +140,10 @@ def test_trns_l(tmp_path): f = str(tmp_path / "temp.png") + im_la = im.convert("LA") + assert "transparency" not in im_la.info + im_la.save(f) + im_rgb = im.convert("RGB") assert im_rgb.info["transparency"] == (128, 128, 128) # undone im_rgb.save(f) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index dc3caef01..281d5a6fb 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -67,6 +67,16 @@ class TestImagingPaste: ], ) + @cached_property + def gradient_LA(self): + return Image.merge( + "LA", + [ + self.gradient_L, + self.gradient_L.transpose(Image.Transpose.ROTATE_90), + ], + ) + @cached_property def gradient_RGBA(self): return Image.merge( @@ -145,6 +155,28 @@ class TestImagingPaste: ], ) + def test_image_mask_LA(self): + for mode in ("RGBA", "RGB", "L"): + im = Image.new(mode, (200, 200), "white") + im2 = getattr(self, "gradient_" + mode) + + self.assert_9points_paste( + im, + im2, + self.gradient_LA, + [ + (128, 191, 255, 191), + (112, 207, 206, 111), + (128, 254, 128, 1), + (208, 208, 239, 239), + (192, 191, 191, 191), + (207, 207, 112, 113), + (255, 255, 255, 255), + (239, 207, 207, 239), + (255, 191, 128, 191), + ], + ) + def test_image_mask_RGBA(self): for mode in ("RGBA", "RGB", "L"): im = Image.new(mode, (200, 200), "white") diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 366f45854..428ad116b 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -10,6 +10,7 @@ def test_sanity(): im.point(list(range(256))) im.point(list(range(256)) * 3) im.point(lambda x: x) + im.point(lambda x: x * 1.2) im = im.convert("I") with pytest.raises(ValueError): diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 7e4bbaaec..3d60e52a2 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -38,7 +38,7 @@ def test_long_integers(): assert put(0xFFFFFFFF) == (255, 255, 255, 255) assert put(-1) == (255, 255, 255, 255) assert put(-1) == (255, 255, 255, 255) - if sys.maxsize > 2 ** 32: + if sys.maxsize > 2**32: assert put(sys.maxsize) == (255, 255, 255, 255) else: assert put(sys.maxsize) == (255, 255, 255, 127) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 04b7c8c97..6961afa60 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -264,6 +264,13 @@ class TestImageResize: with pytest.raises(ValueError): im.resize((10, 10), "unknown") + def test_load_first(self): + # load() may change the size of the image + # Test that resize() is calling it before getting the size + with Image.open("Tests/images/g4_orientation_5.tif") as im: + im = im.resize((64, 64)) + assert im.size == (64, 64) + def test_default_filter(self): for mode in "L", "RGB", "I", "F": im = hopper(mode) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 6d4eb4cd1..858db9a0a 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -88,6 +88,14 @@ def test_no_resize(): assert im.size == (64, 64) +def test_load_first(): + # load() may change the size of the image + # Test that thumbnail() is calling it before performing size calculations + with Image.open("Tests/images/g4_orientation_5.tif") as im: + im.thumbnail((64, 64)) + assert im.size == (64, 10) + + # valgrind test is failing with memory allocated in libjpeg @pytest.mark.valgrind_known_error(reason="Known Failing") def test_DCT_scaling_edges(): @@ -130,4 +138,4 @@ def test_reducing_gap_for_DCT_scaling(): with Image.open("Tests/images/hopper.jpg") as im: im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0) - assert_image_equal(ref, im) + assert_image_similar(ref, im, 1.4) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index e0093739c..66a72a90e 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -303,7 +303,7 @@ def test_extended_information(): def assert_truncated_tuple_equal(tup1, tup2, digits=10): # Helper function to reduce precision of tuples of floats # recursively and then check equality. - power = 10 ** digits + power = 10**digits def truncate_tuple(tuple_or_float): return tuple( diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 3cd755cb4..6755d94b8 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1440,3 +1440,15 @@ def test_continuous_horizontal_edges_polygon(): assert_image_equal_tofile( img, expected, "continuous horizontal edges polygon failed" ) + + +def test_discontiguous_corners_polygon(): + img, draw = create_base_image_draw((84, 68)) + draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK) + draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK) + draw.polygon( + ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)), + BLACK, + ) + expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png") + assert_image_similar_tofile(img, expected, 1) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index f3da73e38..1c444fe27 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -200,6 +200,9 @@ class MockPyEncoder(ImageFile.PyEncoder): def encode(self, buffer): return 1, 1, b"" + def cleanup(self): + self.cleanup_called = True + xoff, yoff, xsize, ysize = 10, 20, 100, 100 @@ -327,10 +330,12 @@ class TestPyEncoder(CodecsTest): im = MockImageFile(buf) fp = BytesIO() + self.encoder.cleanup_called = False with pytest.raises(ValueError): ImageFile._save( im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")] ) + assert self.encoder.cleanup_called with pytest.raises(ValueError): ImageFile._save( diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 368c2bba1..8f6ce7f8f 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -48,10 +48,6 @@ def img_string_normalize(im): return img_to_string(string_to_img(im)) -def assert_img_equal(A, B): - assert img_to_string(A) == img_to_string(B) - - def assert_img_equal_img_string(A, Bstring): assert img_to_string(A) == img_string_normalize(Bstring) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 6aa1cf35e..87fffa7b7 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -63,6 +63,7 @@ def test_sanity(): ImageOps.grayscale(hopper("L")) ImageOps.grayscale(hopper("RGB")) + ImageOps.invert(hopper("1")) ImageOps.invert(hopper("L")) ImageOps.invert(hopper("RGB")) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 930907939..a42240d49 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -32,10 +32,10 @@ def test_rgb(): def checkrgb(r, g, b): val = ImageQt.rgb(r, g, b) - val = val % 2 ** 24 # drop the alpha + val = val % 2**24 # drop the alpha assert val >> 16 == r - assert ((val >> 8) % 2 ** 8) == g - assert val % 2 ** 8 == b + assert ((val >> 8) % 2**8) == g + assert val % 2**8 == b checkrgb(0, 0, 0) checkrgb(255, 0, 0) diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 9474ff6f9..5717fe150 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -51,8 +51,8 @@ def test_constant(): st = ImageStat.Stat(im) assert st.extrema[0] == (128, 128) - assert st.sum[0] == 128 ** 3 - assert st.sum2[0] == 128 ** 4 + assert st.sum[0] == 128**3 + assert st.sum2[0] == 128**4 assert st.mean[0] == 128 assert st.median[0] == 128 assert st.rms[0] == 128 diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 928b8cbd1..9df66df2d 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -75,8 +75,16 @@ def test_photoimage_blank(): assert im_tk.width() == 100 assert im_tk.height() == 100 - # reloaded = ImageTk.getimage(im_tk) - # assert_image_equal(reloaded, im) + im = Image.new(mode, (100, 100)) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded.convert(mode), im) + + +def test_box_deprecation(): + im = hopper() + im_tk = ImageTk.PhotoImage(im) + with pytest.warns(DeprecationWarning): + im_tk.paste(im, (0, 0, 128, 128)) def test_bitmapimage(): diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index c51a66089..df1305655 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -1,4 +1,3 @@ -import ctypes from io import BytesIO from PIL import Image, ImageWin @@ -8,6 +7,7 @@ from .helper import hopper, is_win32 # see https://github.com/python-pillow/Pillow/pull/1431#issuecomment-144692652 if is_win32(): + import ctypes import ctypes.wintypes class BITMAPFILEHEADER(ctypes.Structure): diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index af7eae935..979806cae 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -444,6 +444,8 @@ class TestLibUnpack: self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) + self.assert_unpack("RGBA", "BGRA;16L", 8, (6, 4, 2, 8), (14, 12, 10, 16)) + self.assert_unpack("RGBA", "BGRA;16B", 8, (5, 3, 1, 7), (13, 11, 9, 15)) self.assert_unpack( "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12) ) diff --git a/Tests/test_map.py b/Tests/test_map.py index 42f3447eb..d816bddaf 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -36,7 +36,7 @@ def test_tobytes(): Image.MAX_IMAGE_PIXELS = max_pixels -@pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") +@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_ysize(): numpy = pytest.importorskip("numpy", reason="NumPy not installed") diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index 2d428e95f..ea9b33dfc 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -115,6 +115,6 @@ def test_pdf_repr(): assert pdf_repr(True) == b"true" assert pdf_repr(False) == b"false" assert pdf_repr(None) == b"null" - assert pdf_repr(b"a)/b\\(c") == br"(a\)/b\\\(c)" + assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)" assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]" assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>" diff --git a/docs/conf.py b/docs/conf.py index 7bbe8c4c9..2ed236b18 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,8 +16,6 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import sphinx_rtd_theme - import PIL # -- General configuration ------------------------------------------------ @@ -126,13 +124,15 @@ nitpicky = True # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +html_theme_options = { + "light_logo": "pillow-logo-dark-text.png", + "dark_logo": "pillow-logo.png", +} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -146,7 +146,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = "resources/pillow-logo.png" +# html_logo = "resources/pillow-logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -311,10 +311,7 @@ texinfo_documents = [ def setup(app): - app.add_js_file("js/script.js") - app.add_css_file("css/styles.css") app.add_css_file("css/dark.css") - app.add_css_file("css/light.css") # GitHub repo for sphinx-issues diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 0b82e4185..d4d5907ea 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -69,7 +69,7 @@ In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged. Constants ~~~~~~~~~ -.. deprecated:: 9.2.0 +.. deprecated:: 9.1.0 A number of constants have been deprecated and will be removed in Pillow 10.0.0 (2023-07-01). Instead, ``enum.IntEnum`` classes have been added. @@ -142,6 +142,13 @@ The stub image plugin ``FitsStubImagePlugin`` has been deprecated and will be re Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through :mod:`~PIL.FitsImagePlugin` instead. +PhotoImage.paste box parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 9.2.0 + +The ``box`` parameter is unused. It will be removed in Pillow 10.0.0 (2023-07-01). + Removed features ---------------- diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 272409416..ec3938b36 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -210,7 +210,9 @@ class DdsImageFile(ImageFile.ImageFile): format_description = "DirectDraw Surface" def _open(self): - magic, header_size = struct.unpack("fd`` will be a pointer to the Python file like object. The - decoder may use the functions in ``codec_fd.c`` to read directly - from the file like object rather than have the data pushed through a - buffer. Note that this implementation may be refactored until this - warning is removed. +**pulls_fd**/**pushes_fd** + If the decoder has ``pulls_fd`` or the encoder has ``pushes_fd`` set to 1, + ``state->fd`` will be a pointer to the Python file like object. The codec may + use the functions in ``codec_fd.c`` to read or write directly with the file + like object rather than have the data pushed through a buffer. .. versionadded:: 3.3.0 -Decoding --------- +Transforming +------------ -The decode function is called with the target (core) image, the -decoder state structure, and a buffer of data to be decoded. +The decode or encode function is called with the target (core) image, the codec +state structure, and a buffer of data to be transformed. -**Experimental** -- If ``pulls_fd`` is set, then the decode function -is called once, with an empty buffer. It is the decoder's -responsibility to decode the entire tile in that one call. The rest of -this section only applies if ``pulls_fd`` is not set. +It is the codec's responsibility to pull as much data as possible out of the +buffer and return the number of bytes consumed. The next call to the codec will +include the previous unconsumed tail. The codec function will be called +multiple times as the data processed. -It is the decoder's responsibility to pull as much data as possible -out of the buffer and return the number of bytes consumed. The next -call to the decoder will include the previous unconsumed tail. The -decoder function will be called multiple times as the data is read -from the file like object. +Alternatively, if ``pulls_fd`` or ``pushes_fd`` is set, then the decode or +encode function is called once, with an empty buffer. It is the codec's +responsibility to transform the entire tile in that one call. Using this will +provide a codec with more freedom, but that freedom may mean increased memory +usage if the entire tile is held in memory at once by the codec. If an error occurs, set ``state->errcode`` and return -1. @@ -407,10 +409,9 @@ Return -1 on success, without setting the errcode. Cleanup ------- -The cleanup function is called after the decoder returns a negative -value, or if there is a read error from the file. This function should -free any allocated memory and release any resources from external -libraries. +The cleanup function is called after the codec returns a negative +value, or if there is an error. This function should free any allocated +memory and release any resources from external libraries. .. _file-codecs-py: @@ -425,11 +426,32 @@ They should be registered using :py:meth:`PIL.Image.register_decoder` and the file codecs, there are three stages in the lifetime of a Python-based file codec: -1. Setup: Pillow looks for the decoder in the registry, then +1. Setup: Pillow looks for the codec in the decoder or encoder registry, then instantiates the class. 2. Transforming: The instance's ``decode`` method is repeatedly called with a buffer of data to be interpreted, or the ``encode`` method is repeatedly called with the size of data to be output. -3. Cleanup: The instance's ``cleanup`` method is called. + Alternatively, if the decoder's ``_pulls_fd`` property (or the encoder's + ``_pushes_fd`` property) is set to ``True``, then ``decode`` and ``encode`` + will only be called once. In the decoder, ``self.fd`` can be used to access + the file-like object. Using this will provide a codec with more freedom, but + that freedom may mean increased memory usage if entire file is held in + memory at once by the codec. + + In ``decode``, once the data has been interpreted, ``set_as_raw`` can be + used to populate the image. + +3. Cleanup: The instance's ``cleanup`` method is called once the transformation + is complete. This can be used to clean up any resources used by the codec. + + If you set ``_pulls_fd`` or ``_pushes_fd`` to ``True`` however, then you + probably chose to perform any cleanup tasks at the end of ``decode`` or + ``encode``. + +For an example :py:class:`PIL.ImageFile.PyDecoder`, see `DdsImagePlugin +`_. +For a plugin that uses both :py:class:`PIL.ImageFile.PyDecoder` and +:py:class:`PIL.ImageFile.PyEncoder`, see `BlpImagePlugin +`_ diff --git a/docs/installation.rst b/docs/installation.rst index 3e0446a5d..e5f09dad4 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -461,8 +461,6 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 11 Bullseye | 3.9 | x86 | +----------------------------------+----------------------------+---------------------+ -| Fedora 34 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Fedora 35 | 3.10 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index 5bb735296..f61d12313 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -14,6 +14,16 @@ for a region of an image. statistics. You can also pass in a previously calculated histogram. :param image: A PIL image, or a precalculated histogram. + + .. note:: + + For a PIL image, calculations rely on the + :py:meth:`~PIL.Image.Image.histogram` method. The pixel counts are + grouped into 256 bins, even if the image has more than 8 bits per + channel. So ``I`` and ``F`` mode images have a maximum ``mean``, + ``median`` and ``rms`` of 255, and cannot have an ``extrema`` maximum + of more than 255. + :param mask: An optional mask. .. py:attribute:: extrema diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index 173a0bcc0..d2e80fb8c 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -6,7 +6,13 @@ The PixelAccess class provides read and write access to :py:class:`PIL.Image` data at a pixel level. -.. note:: Accessing individual pixels is fairly slow. If you are looping over all of the pixels in an image, there is likely a faster way using other parts of the Pillow API. +.. note:: Accessing individual pixels is fairly slow. If you are + looping over all of the pixels in an image, there is likely + a faster way using other parts of the Pillow API. + + :mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps` + have methods for many standard operations. If you wish to perform + a custom mapping, check out :py:meth:`~PIL.Image.Image.point`. Example ------- @@ -39,7 +45,7 @@ Access using negative indexes is also possible. :py:class:`PixelAccess` Class ------------------------------------ +----------------------------- .. class:: PixelAccess diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index e77944d20..f9eb9b524 100644 --- a/docs/reference/PyAccess.rst +++ b/docs/reference/PyAccess.rst @@ -7,8 +7,12 @@ The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version. .. note:: Accessing individual pixels is fairly slow. If you are - looping over all of the pixels in an image, there is likely - a faster way using other parts of the Pillow API. + looping over all of the pixels in an image, there is likely + a faster way using other parts of the Pillow API. + + :mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps` + have methods for many standard operations. If you wish to perform + a custom mapping, check out :py:meth:`~PIL.Image.Image.point`. Example ------- diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 22b185e95..80ce7604f 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -146,12 +146,24 @@ At present, the information within each block is merely returned as a dictionary "data" entry. This will allow more useful information to be added in the future without breaking backwards compatibility. -Added rawmode argument to Image.getpalette() -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Added mct and no_jp2 options for saving JPEG 2000 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, :py:meth:`~PIL.Image.Image.getpalette` returns RGB data from the palette. -A ``rawmode`` argument has been added, to allow the mode to be chosen instead. ``None`` -can be used to return data in the current mode of the palette. +The :py:meth:`PIL.Image.Image.save` method now supports the following options for +JPEG 2000: + +**mct** + If ``1`` then enable multiple component transformation when encoding, + otherwise use ``0`` for no component transformation (default). If MCT is + enabled and ``irreversible`` is ``True`` then the Irreversible Color + Transformation will be applied, otherwise encoding will use the + Reversible Color Transformation. MCT works best with a ``mode`` of + ``RGB`` and is only applicable when the image data has 3 components. + +**no_jp2** + If ``True`` then don't wrap the raw codestream in the JP2 file format when + saving, otherwise the extension of the filename will be used to determine + the format (default). Added PyEncoder ^^^^^^^^^^^^^^^ @@ -160,9 +172,35 @@ Added PyEncoder written in Python. See :ref:`Writing Your Own File Codec in Python` for more information. +GifImagePlugin loading strategy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow 9.0.0 introduced the conversion of subsequent GIF frames to ``RGB`` or ``RGBA``. This +behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as +well. + +.. code-block:: python + + from PIL import GifImagePlugin + GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS + +Or subsequent frames can be kept in ``P`` mode as long as there is only a single +palette. + +.. code-block:: python + + from PIL import GifImagePlugin + GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY + Other Changes ============= +musllinux wheels +^^^^^^^^^^^^^^^^ + +Pillow now builds binary wheels for musllinux, suitable for Linux distributions based on the musl C standard library such as Alpine +(rather than the glibc library used by manylinux wheels). See :pep:`656`. + ImageShow temporary files on Unix ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -177,6 +215,11 @@ Image._repr_pretty_ identity of the object. This allows Jupyter to describe an image and have that description stay the same on subsequent executions of the same code. +Added BigTIFF reading +^^^^^^^^^^^^^^^^^^^^^ + +Support has been added for reading BigTIFF images. + Added BLP saving ^^^^^^^^^^^^^^^^ diff --git a/docs/resources/css/dark.css b/docs/resources/css/dark.css index 8866c07ea..1571cbc4e 100644 --- a/docs/resources/css/dark.css +++ b/docs/resources/css/dark.css @@ -1,1996 +1,9 @@ +/* For black-on-white/transparent images at handbook/text-anchors.html */ +body[data-theme="dark"] #text-anchors img { + filter: invert(1) brightness(0.85) hue-rotate(-60deg); +} @media (prefers-color-scheme: dark) { - html { - background-color: #181a1b !important; - } - - html, body, input, textarea, select, button { - background-color: #181a1b; - } - - html, body, input, textarea, select, button { - border-color: #736b5e; - color: #e8e6e3; - } - - a { - color: #3391ff; - } - - table { - border-color: #545b5e; - } - - ::placeholder { - color: #b2aba1; - } - - input:-webkit-autofill, - textarea:-webkit-autofill, - select:-webkit-autofill { - background-color: #555b00 !important; - color: #e8e6e3 !important; - } - - ::selection { - background-color: #004daa !important; - color: #e8e6e3 !important; - } - - ::-moz-selection { - background-color: #004daa !important; - color: #e8e6e3 !important; - } - - /* Invert Style */ - .jfk-bubble.gtx-bubble, embed[type="application/pdf"] { - filter: invert(100%) hue-rotate(180deg) contrast(90%) !important; - } - - /* Override Style */ - .vimvixen-hint { - background-color: #7b5300 !important; - border-color: #d8b013 !important; - color: #f3e8c8 !important; - } - - ::placeholder { - opacity: 0.5 !important; - } - - /* Variables Style */ - :root { - --darkreader-neutral-background: #181a1b; - --darkreader-neutral-text: #e8e6e3; - --darkreader-selection-background: #004daa; - --darkreader-selection-text: #e8e6e3; - } - - /* Modified CSS */ - a:hover, - a:active { - outline-color: initial; - } - - abbr[title] { - border-bottom-color: initial; - } - - ins { - background-image: initial; - background-color: rgb(112, 112, 0); - color: rgb(232, 230, 227); - text-decoration-color: initial; - } - - mark { - background-image: initial; - background-color: rgb(204, 204, 0); - color: rgb(232, 230, 227); - } - - ul, - ol, - dl { - list-style-image: none; - } - - li { - list-style-image: initial; - } - - img { - border-color: initial; - } - - fieldset { - border-color: initial; - } - - legend { - border-color: initial; - } - - .chromeframe { - background-image: initial; - background-color: rgb(53, 57, 59); - color: rgb(232, 230, 227); - } - - .ir { - border-color: initial; - background-color: transparent; - } - - .visuallyhidden { - border-color: initial; - } - - .fa-border { - border-color: rgb(53, 57, 59); - } - - .fa-inverse { - color: rgb(232, 230, 227); - } - - .sr-only { - border-color: initial; - } - - .fa::before, - .wy-menu-vertical li span.toctree-expand::before, - .wy-menu-vertical li.on a span.toctree-expand::before, - .wy-menu-vertical li.current > a span.toctree-expand::before, - .rst-content .admonition-title::before, - .rst-content h1 .headerlink::before, - .rst-content h2 .headerlink::before, - .rst-content h3 .headerlink::before, - .rst-content h4 .headerlink::before, - .rst-content h5 .headerlink::before, - .rst-content h6 .headerlink::before, - .rst-content dl dt .headerlink::before, - .rst-content p.caption .headerlink::before, - .rst-content table > caption .headerlink::before, - .rst-content .code-block-caption .headerlink::before, - .rst-content tt.download span:first-child::before, - .rst-content code.download span:first-child::before, - .icon::before, - .wy-dropdown .caret::before, - .wy-inline-validate.wy-inline-validate-success .wy-input-context::before, - .wy-inline-validate.wy-inline-validate-danger .wy-input-context::before, - .wy-inline-validate.wy-inline-validate-warning .wy-input-context::before, - .wy-inline-validate.wy-inline-validate-info .wy-input-context::before { - text-decoration-color: inherit; - } - - a .fa, - a .wy-menu-vertical li span.toctree-expand, - .wy-menu-vertical li a span.toctree-expand, - .wy-menu-vertical li.on a span.toctree-expand, - .wy-menu-vertical li.current > a span.toctree-expand, - a .rst-content .admonition-title, - .rst-content a .admonition-title, - a .rst-content h1 .headerlink, - .rst-content h1 a .headerlink, - a .rst-content h2 .headerlink, - .rst-content h2 a .headerlink, - a .rst-content h3 .headerlink, - .rst-content h3 a .headerlink, - a .rst-content h4 .headerlink, - .rst-content h4 a .headerlink, - a .rst-content h5 .headerlink, - .rst-content h5 a .headerlink, - a .rst-content h6 .headerlink, - .rst-content h6 a .headerlink, - a .rst-content dl dt .headerlink, - .rst-content dl dt a .headerlink, - a .rst-content p.caption .headerlink, - .rst-content p.caption a .headerlink, - a .rst-content table > caption .headerlink, - .rst-content table > caption a .headerlink, - a .rst-content .code-block-caption .headerlink, - .rst-content .code-block-caption a .headerlink, - a .rst-content tt.download span:first-child, - .rst-content tt.download a span:first-child, - a .rst-content code.download span:first-child, - .rst-content code.download a span:first-child, - a .icon { - text-decoration-color: inherit; - } - - .wy-alert, - .rst-content .note, - .rst-content .attention, - .rst-content .caution, - .rst-content .danger, - .rst-content .error, - .rst-content .hint, - .rst-content .important, - .rst-content .tip, - .rst-content .warning, - .rst-content .seealso, - .rst-content .admonition-todo, - .rst-content .admonition { - background-image: initial; - background-color: rgb(32, 35, 36); - } - - .wy-alert-title, - .rst-content .admonition-title { - color: rgb(232, 230, 227); - background-image: initial; - background-color: rgb(29, 91, 131); - } - - .wy-alert.wy-alert-danger, - .rst-content .wy-alert-danger.note, - .rst-content .wy-alert-danger.attention, - .rst-content .wy-alert-danger.caution, - .rst-content .danger, - .rst-content .error, - .rst-content .wy-alert-danger.hint, - .rst-content .wy-alert-danger.important, - .rst-content .wy-alert-danger.tip, - .rst-content .wy-alert-danger.warning, - .rst-content .wy-alert-danger.seealso, - .rst-content .wy-alert-danger.admonition-todo, - .rst-content .wy-alert-danger.admonition { - background-image: initial; - background-color: rgb(52, 12, 8); - } - - .wy-alert.wy-alert-danger .wy-alert-title, - .rst-content .wy-alert-danger.note .wy-alert-title, - .rst-content .wy-alert-danger.attention .wy-alert-title, - .rst-content .wy-alert-danger.caution .wy-alert-title, - .rst-content .danger .wy-alert-title, - .rst-content .error .wy-alert-title, - .rst-content .wy-alert-danger.hint .wy-alert-title, - .rst-content .wy-alert-danger.important .wy-alert-title, - .rst-content .wy-alert-danger.tip .wy-alert-title, - .rst-content .wy-alert-danger.warning .wy-alert-title, - .rst-content .wy-alert-danger.seealso .wy-alert-title, - .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, - .rst-content .wy-alert-danger.admonition .wy-alert-title, - .wy-alert.wy-alert-danger .rst-content .admonition-title, - .rst-content .wy-alert.wy-alert-danger .admonition-title, - .rst-content .wy-alert-danger.note .admonition-title, - .rst-content .wy-alert-danger.attention .admonition-title, - .rst-content .wy-alert-danger.caution .admonition-title, - .rst-content .danger .admonition-title, - .rst-content .error .admonition-title, - .rst-content .wy-alert-danger.hint .admonition-title, - .rst-content .wy-alert-danger.important .admonition-title, - .rst-content .wy-alert-danger.tip .admonition-title, - .rst-content .wy-alert-danger.warning .admonition-title, - .rst-content .wy-alert-danger.seealso .admonition-title, - .rst-content .wy-alert-danger.admonition-todo .admonition-title, - .rst-content .wy-alert-danger.admonition .admonition-title { - background-image: initial; - background-color: rgb(108, 22, 13); - } - - .wy-alert.wy-alert-warning, - .rst-content .wy-alert-warning.note, - .rst-content .attention, - .rst-content .caution, - .rst-content .wy-alert-warning.danger, - .rst-content .wy-alert-warning.error, - .rst-content .wy-alert-warning.hint, - .rst-content .wy-alert-warning.important, - .rst-content .wy-alert-warning.tip, - .rst-content .warning, - .rst-content .wy-alert-warning.seealso, - .rst-content .admonition-todo, - .rst-content .wy-alert-warning.admonition { - background-image: initial; - background-color: rgb(82, 53, 0); - } - - .wy-alert.wy-alert-warning .wy-alert-title, - .rst-content .wy-alert-warning.note .wy-alert-title, - .rst-content .attention .wy-alert-title, - .rst-content .caution .wy-alert-title, - .rst-content .wy-alert-warning.danger .wy-alert-title, - .rst-content .wy-alert-warning.error .wy-alert-title, - .rst-content .wy-alert-warning.hint .wy-alert-title, - .rst-content .wy-alert-warning.important .wy-alert-title, - .rst-content .wy-alert-warning.tip .wy-alert-title, - .rst-content .warning .wy-alert-title, - .rst-content .wy-alert-warning.seealso .wy-alert-title, - .rst-content .admonition-todo .wy-alert-title, - .rst-content .wy-alert-warning.admonition .wy-alert-title, - .wy-alert.wy-alert-warning .rst-content .admonition-title, - .rst-content .wy-alert.wy-alert-warning .admonition-title, - .rst-content .wy-alert-warning.note .admonition-title, - .rst-content .attention .admonition-title, - .rst-content .caution .admonition-title, - .rst-content .wy-alert-warning.danger .admonition-title, - .rst-content .wy-alert-warning.error .admonition-title, - .rst-content .wy-alert-warning.hint .admonition-title, - .rst-content .wy-alert-warning.important .admonition-title, - .rst-content .wy-alert-warning.tip .admonition-title, - .rst-content .warning .admonition-title, - .rst-content .wy-alert-warning.seealso .admonition-title, - .rst-content .admonition-todo .admonition-title, - .rst-content .wy-alert-warning.admonition .admonition-title { - background-image: initial; - background-color: rgb(123, 65, 14); - } - - .wy-alert.wy-alert-info, - .rst-content .note, - .rst-content .wy-alert-info.attention, - .rst-content .wy-alert-info.caution, - .rst-content .wy-alert-info.danger, - .rst-content .wy-alert-info.error, - .rst-content .wy-alert-info.hint, - .rst-content .wy-alert-info.important, - .rst-content .wy-alert-info.tip, - .rst-content .wy-alert-info.warning, - .rst-content .seealso, - .rst-content .wy-alert-info.admonition-todo, - .rst-content .wy-alert-info.admonition { - background-image: initial; - background-color: rgb(32, 35, 36); - } - - .wy-alert.wy-alert-info .wy-alert-title, - .rst-content .note .wy-alert-title, - .rst-content .wy-alert-info.attention .wy-alert-title, - .rst-content .wy-alert-info.caution .wy-alert-title, - .rst-content .wy-alert-info.danger .wy-alert-title, - .rst-content .wy-alert-info.error .wy-alert-title, - .rst-content .wy-alert-info.hint .wy-alert-title, - .rst-content .wy-alert-info.important .wy-alert-title, - .rst-content .wy-alert-info.tip .wy-alert-title, - .rst-content .wy-alert-info.warning .wy-alert-title, - .rst-content .seealso .wy-alert-title, - .rst-content .wy-alert-info.admonition-todo .wy-alert-title, - .rst-content .wy-alert-info.admonition .wy-alert-title, - .wy-alert.wy-alert-info .rst-content .admonition-title, - .rst-content .wy-alert.wy-alert-info .admonition-title, - .rst-content .note .admonition-title, - .rst-content .wy-alert-info.attention .admonition-title, - .rst-content .wy-alert-info.caution .admonition-title, - .rst-content .wy-alert-info.danger .admonition-title, - .rst-content .wy-alert-info.error .admonition-title, - .rst-content .wy-alert-info.hint .admonition-title, - .rst-content .wy-alert-info.important .admonition-title, - .rst-content .wy-alert-info.tip .admonition-title, - .rst-content .wy-alert-info.warning .admonition-title, - .rst-content .seealso .admonition-title, - .rst-content .wy-alert-info.admonition-todo .admonition-title, - .rst-content .wy-alert-info.admonition .admonition-title { - background-image: initial; - background-color: rgb(29, 91, 131); - } - - .wy-alert.wy-alert-success, - .rst-content .wy-alert-success.note, - .rst-content .wy-alert-success.attention, - .rst-content .wy-alert-success.caution, - .rst-content .wy-alert-success.danger, - .rst-content .wy-alert-success.error, - .rst-content .hint, - .rst-content .important, - .rst-content .tip, - .rst-content .wy-alert-success.warning, - .rst-content .wy-alert-success.seealso, - .rst-content .wy-alert-success.admonition-todo, - .rst-content .wy-alert-success.admonition { - background-image: initial; - background-color: rgb(9, 66, 58); - } - - .wy-alert.wy-alert-success .wy-alert-title, - .rst-content .wy-alert-success.note .wy-alert-title, - .rst-content .wy-alert-success.attention .wy-alert-title, - .rst-content .wy-alert-success.caution .wy-alert-title, - .rst-content .wy-alert-success.danger .wy-alert-title, - .rst-content .wy-alert-success.error .wy-alert-title, - .rst-content .hint .wy-alert-title, - .rst-content .important .wy-alert-title, - .rst-content .tip .wy-alert-title, - .rst-content .wy-alert-success.warning .wy-alert-title, - .rst-content .wy-alert-success.seealso .wy-alert-title, - .rst-content .wy-alert-success.admonition-todo .wy-alert-title, - .rst-content .wy-alert-success.admonition .wy-alert-title, - .wy-alert.wy-alert-success .rst-content .admonition-title, - .rst-content .wy-alert.wy-alert-success .admonition-title, - .rst-content .wy-alert-success.note .admonition-title, - .rst-content .wy-alert-success.attention .admonition-title, - .rst-content .wy-alert-success.caution .admonition-title, - .rst-content .wy-alert-success.danger .admonition-title, - .rst-content .wy-alert-success.error .admonition-title, - .rst-content .hint .admonition-title, - .rst-content .important .admonition-title, - .rst-content .tip .admonition-title, - .rst-content .wy-alert-success.warning .admonition-title, - .rst-content .wy-alert-success.seealso .admonition-title, - .rst-content .wy-alert-success.admonition-todo .admonition-title, - .rst-content .wy-alert-success.admonition .admonition-title { - background-image: initial; - background-color: rgb(21, 150, 125); - } - - .wy-alert.wy-alert-neutral, - .rst-content .wy-alert-neutral.note, - .rst-content .wy-alert-neutral.attention, - .rst-content .wy-alert-neutral.caution, - .rst-content .wy-alert-neutral.danger, - .rst-content .wy-alert-neutral.error, - .rst-content .wy-alert-neutral.hint, - .rst-content .wy-alert-neutral.important, - .rst-content .wy-alert-neutral.tip, - .rst-content .wy-alert-neutral.warning, - .rst-content .wy-alert-neutral.seealso, - .rst-content .wy-alert-neutral.admonition-todo, - .rst-content .wy-alert-neutral.admonition { - background-image: initial; - background-color: rgb(27, 36, 36); - } - - .wy-alert.wy-alert-neutral .wy-alert-title, - .rst-content .wy-alert-neutral.note .wy-alert-title, - .rst-content .wy-alert-neutral.attention .wy-alert-title, - .rst-content .wy-alert-neutral.caution .wy-alert-title, - .rst-content .wy-alert-neutral.danger .wy-alert-title, - .rst-content .wy-alert-neutral.error .wy-alert-title, - .rst-content .wy-alert-neutral.hint .wy-alert-title, - .rst-content .wy-alert-neutral.important .wy-alert-title, - .rst-content .wy-alert-neutral.tip .wy-alert-title, - .rst-content .wy-alert-neutral.warning .wy-alert-title, - .rst-content .wy-alert-neutral.seealso .wy-alert-title, - .rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, - .rst-content .wy-alert-neutral.admonition .wy-alert-title, - .wy-alert.wy-alert-neutral .rst-content .admonition-title, - .rst-content .wy-alert.wy-alert-neutral .admonition-title, - .rst-content .wy-alert-neutral.note .admonition-title, - .rst-content .wy-alert-neutral.attention .admonition-title, - .rst-content .wy-alert-neutral.caution .admonition-title, - .rst-content .wy-alert-neutral.danger .admonition-title, - .rst-content .wy-alert-neutral.error .admonition-title, - .rst-content .wy-alert-neutral.hint .admonition-title, - .rst-content .wy-alert-neutral.important .admonition-title, - .rst-content .wy-alert-neutral.tip .admonition-title, - .rst-content .wy-alert-neutral.warning .admonition-title, - .rst-content .wy-alert-neutral.seealso .admonition-title, - .rst-content .wy-alert-neutral.admonition-todo .admonition-title, - .rst-content .wy-alert-neutral.admonition .admonition-title { - color: rgb(192, 186, 178); - background-image: initial; - background-color: rgb(40, 43, 45); - } - - .wy-alert.wy-alert-neutral a, - .rst-content .wy-alert-neutral.note a, - .rst-content .wy-alert-neutral.attention a, - .rst-content .wy-alert-neutral.caution a, - .rst-content .wy-alert-neutral.danger a, - .rst-content .wy-alert-neutral.error a, - .rst-content .wy-alert-neutral.hint a, - .rst-content .wy-alert-neutral.important a, - .rst-content .wy-alert-neutral.tip a, - .rst-content .wy-alert-neutral.warning a, - .rst-content .wy-alert-neutral.seealso a, - .rst-content .wy-alert-neutral.admonition-todo a, - .rst-content .wy-alert-neutral.admonition a { - color: rgb(84, 164, 217); - } - - .wy-tray-container li { - background-image: initial; - background-color: transparent; - color: rgb(232, 230, 227); - box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px 0px; - } - - .wy-tray-container li.wy-tray-item-success { - background-image: initial; - background-color: rgb(31, 139, 77); - } - - .wy-tray-container li.wy-tray-item-info { - background-image: initial; - background-color: rgb(33, 102, 148); - } - - .wy-tray-container li.wy-tray-item-warning { - background-image: initial; - background-color: rgb(178, 94, 20); - } - - .wy-tray-container li.wy-tray-item-danger { - background-image: initial; - background-color: rgb(162, 33, 20); - } - - .btn { - color: rgb(232, 230, 227); - border-color: rgba(140, 130, 115, 0.1); - background-color: rgb(31, 139, 77); - text-decoration-color: initial; - box-shadow: rgba(24, 26, 27, 0.5) 0px 1px 2px -1px inset, - rgba(0, 0, 0, 0.1) 0px -2px 0px 0px inset; - } - - .btn-hover { - background-image: initial; - background-color: rgb(37, 114, 165); - color: rgb(232, 230, 227); - } - - .btn:hover { - background-image: initial; - background-color: rgb(35, 156, 86); - color: rgb(232, 230, 227); - } - - .btn:focus { - background-image: initial; - background-color: rgb(35, 156, 86); - outline-color: initial; - } - - .btn:active { - box-shadow: rgba(0, 0, 0, 0.05) 0px -1px 0px 0px inset, - rgba(0, 0, 0, 0.1) 0px 2px 0px 0px inset; - } - - .btn:visited { - color: rgb(232, 230, 227); - } - - .btn:disabled { - background-image: none; - box-shadow: none; - } - - .btn-disabled { - background-image: none; - box-shadow: none; - } - - .btn-disabled:hover, - .btn-disabled:focus, - .btn-disabled:active { - background-image: none; - box-shadow: none; - } - - .btn-info { - background-color: rgb(33, 102, 148) !important; - } - - .btn-info:hover { - background-color: rgb(37, 114, 165) !important; - } - - .btn-neutral { - background-color: rgb(27, 36, 36) !important; - color: rgb(192, 186, 178) !important; - } - - .btn-neutral:hover { - color: rgb(192, 186, 178); - background-color: rgb(34, 44, 44) !important; - } - - .btn-neutral:visited { - color: rgb(192, 186, 178) !important; - } - - .btn-success { - background-color: rgb(31, 139, 77) !important; - } - - .btn-success:hover { - background-color: rgb(27, 122, 68) !important; - } - - .btn-danger { - background-color: rgb(162, 33, 20) !important; - } - - .btn-danger:hover { - background-color: rgb(149, 30, 18) !important; - } - - .btn-warning { - background-color: rgb(178, 94, 20) !important; - } - - .btn-warning:hover { - background-color: rgb(165, 87, 18) !important; - } - - .btn-invert { - background-color: rgb(26, 28, 29); - } - - .btn-invert:hover { - background-color: rgb(35, 38, 40) !important; - } - - .btn-link { - color: rgb(84, 164, 217); - box-shadow: none; - background-color: transparent !important; - border-color: transparent !important; - } - - .btn-link:hover { - box-shadow: none; - background-color: transparent !important; - color: rgb(79, 162, 216) !important; - } - - .btn-link:active { - box-shadow: none; - background-color: transparent !important; - color: rgb(79, 162, 216) !important; - } - - .btn-link:visited { - color: rgb(164, 103, 188); - } - - .wy-dropdown-menu { - background-image: initial; - background-color: rgb(26, 28, 29); - border-color: rgb(60, 65, 67); - box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 2px 0px; - } - - .wy-dropdown-menu > dd > a { - color: rgb(192, 186, 178); - } - - .wy-dropdown-menu > dd > a:hover { - background-image: initial; - background-color: rgb(33, 102, 148); - color: rgb(232, 230, 227); - } - - .wy-dropdown-menu > dd.divider { - border-top-color: rgb(60, 65, 67); - } - - .wy-dropdown-menu > dd.call-to-action { - background-image: initial; - background-color: rgb(40, 43, 45); - } - - .wy-dropdown-menu > dd.call-to-action:hover { - background-image: initial; - background-color: rgb(40, 43, 45); - } - - .wy-dropdown-menu > dd.call-to-action .btn { - color: rgb(232, 230, 227); - } - - .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { - background-image: initial; - background-color: rgb(26, 28, 29); - } - - .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { - background-image: initial; - background-color: rgb(33, 102, 148); - color: rgb(232, 230, 227); - } - - .wy-dropdown-arrow::before { - border-bottom-color: rgb(51, 55, 57); - border-left-color: transparent; - border-right-color: transparent; - } - - fieldset { - border-color: initial; - } - - legend { - border-color: initial; - } - - label { - color: rgb(200, 195, 188); - } - - .wy-control-group.wy-control-group-required > label::after { - color: rgb(233, 88, 73); - } - - .wy-form-message-inline { - color: rgb(168, 160, 149); - } - - .wy-form-message { - color: rgb(168, 160, 149); - } - - input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { - border-color: rgb(62, 68, 70); - box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; - } - - input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { - outline-color: initial; - border-color: rgb(123, 114, 101); - } - - input.no-focus:focus { - border-color: rgb(62, 68, 70) !important; - } - - input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { - outline-color: rgb(13, 113, 167); - } - - input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { - background-color: rgb(27, 29, 30); - } - - input:focus:invalid, - textarea:focus:invalid, - select:focus:invalid { - color: rgb(233, 88, 73); - border-color: rgb(149, 31, 18); - } - - input:focus:invalid:focus, - textarea:focus:invalid:focus, - select:focus:invalid:focus { - border-color: rgb(149, 31, 18); - } - - input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { - outline-color: rgb(149, 31, 18); - } - - select, - textarea { - border-color: rgb(62, 68, 70); - box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; - } - - select { - border-color: rgb(62, 68, 70); - background-color: rgb(24, 26, 27); - } - - select:focus, - textarea:focus { - outline-color: initial; - } - - select[disabled], - textarea[disabled], - input[readonly], - select[readonly], - textarea[readonly] { - background-color: rgb(27, 29, 30); - } - - .wy-checkbox, - .wy-radio { - color: rgb(192, 186, 178); - } - - .wy-input-prefix .wy-input-context, - .wy-input-suffix .wy-input-context { - background-color: rgb(27, 36, 36); - border-color: rgb(62, 68, 70); - color: rgb(168, 160, 149); - } - - .wy-input-suffix .wy-input-context { - border-left-color: initial; - } - - .wy-input-prefix .wy-input-context { - border-right-color: initial; - } - - .wy-switch::before { - background-image: initial; - background-color: rgb(53, 57, 59); - } - - .wy-switch::after { - background-image: initial; - background-color: rgb(82, 88, 92); - } - - .wy-switch span { - color: rgb(200, 195, 188); - } - - .wy-switch.active::before { - background-image: initial; - background-color: rgb(24, 106, 58); - } - - .wy-switch.active::after { - background-image: initial; - background-color: rgb(31, 139, 77); - } - - .wy-control-group.wy-control-group-error .wy-form-message, - .wy-control-group.wy-control-group-error > label { - color: rgb(233, 88, 73); - } - - .wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { - border-color: rgb(149, 31, 18); - } - - .wy-control-group.wy-control-group-error textarea { - border-color: rgb(149, 31, 18); - } - - .wy-inline-validate.wy-inline-validate-success .wy-input-context { - color: rgb(92, 218, 145); - } - - .wy-inline-validate.wy-inline-validate-danger .wy-input-context { - color: rgb(233, 88, 73); - } - - .wy-inline-validate.wy-inline-validate-warning .wy-input-context { - color: rgb(232, 138, 54); - } - - .wy-inline-validate.wy-inline-validate-info .wy-input-context { - color: rgb(84, 164, 217); - } - - .wy-table caption, - .rst-content table.docutils caption, - .rst-content table.field-list caption { - color: rgb(232, 230, 227); - } - - .wy-table thead, - .rst-content table.docutils thead, - .rst-content table.field-list thead { - color: rgb(232, 230, 227); - } - - .wy-table thead th, - .rst-content table.docutils thead th, - .rst-content table.field-list thead th { - border-bottom-color: rgb(56, 61, 63); - } - - .wy-table td, - .rst-content table.docutils td, - .rst-content table.field-list td { - background-color: transparent; - } - - .wy-table-secondary { - color: rgb(152, 143, 129); - } - - .wy-table-tertiary { - color: rgb(152, 143, 129); - } - - .wy-table-odd td, - .wy-table-striped tr:nth-child(2n-1) td, - .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { - background-color: rgb(27, 36, 36); - } - - .wy-table-backed { - background-color: rgb(27, 36, 36); - } - - .wy-table-bordered-all, - .rst-content table.docutils { - border-color: rgb(56, 61, 63); - } - - .wy-table-bordered-all td, - .rst-content table.docutils td { - border-bottom-color: rgb(56, 61, 63); - border-left-color: rgb(56, 61, 63); - } - - .wy-table-bordered { - border-color: rgb(56, 61, 63); - } - - .wy-table-bordered-rows td { - border-bottom-color: rgb(56, 61, 63); - } - - .wy-table-horizontal td, - .wy-table-horizontal th { - border-bottom-color: rgb(56, 61, 63); - } - - a { - color: rgb(84, 164, 217); - text-decoration-color: initial; - } - - a:hover { - color: rgb(68, 156, 214); - } - - a:visited { - color: rgb(164, 103, 188); - } - - body { - color: rgb(192, 186, 178); - background-image: initial; - background-color: rgb(33, 35, 37); - } - - .wy-text-strike { - text-decoration-color: initial; - } - - .wy-text-warning { - color: rgb(232, 138, 54) !important; - } - - a.wy-text-warning:hover { - color: rgb(236, 157, 87) !important; - } - - .wy-text-info { - color: rgb(84, 164, 217) !important; - } - - a.wy-text-info:hover { - color: rgb(79, 162, 216) !important; - } - - .wy-text-success { - color: rgb(92, 218, 145) !important; - } - - a.wy-text-success:hover { - color: rgb(73, 214, 133) !important; - } - - .wy-text-danger { - color: rgb(233, 88, 73) !important; - } - - a.wy-text-danger:hover { - color: rgb(237, 118, 104) !important; - } - - .wy-text-neutral { - color: rgb(192, 186, 178) !important; - } - - a.wy-text-neutral:hover { - color: rgb(176, 169, 159) !important; - } - - hr { - border-right-color: initial; - border-bottom-color: initial; - border-left-color: initial; - border-top-color: rgb(56, 61, 63); - } - - code, - .rst-content tt, - .rst-content code { - background-image: initial; - background-color: rgb(24, 26, 27); - border-color: rgb(56, 61, 63); - color: rgb(233, 88, 73); - } - - .wy-plain-list-disc, - .rst-content .section ul, - .rst-content .toctree-wrapper ul, - article ul { - list-style-image: initial; - } - - .wy-plain-list-disc li, - .rst-content .section ul li, - .rst-content .toctree-wrapper ul li, - article ul li { - list-style-image: initial; - } - - .wy-plain-list-disc li li, - .rst-content .section ul li li, - .rst-content .toctree-wrapper ul li li, - article ul li li { - list-style-image: initial; - } - - .wy-plain-list-disc li li li, - .rst-content .section ul li li li, - .rst-content .toctree-wrapper ul li li li, - article ul li li li { - list-style-image: initial; - } - - .wy-plain-list-disc li ol li, - .rst-content .section ul li ol li, - .rst-content .toctree-wrapper ul li ol li, - article ul li ol li { - list-style-image: initial; - } - - .wy-plain-list-decimal, - .rst-content .section ol, - .rst-content ol.arabic, - article ol { - list-style-image: initial; - } - - .wy-plain-list-decimal li, - .rst-content .section ol li, - .rst-content ol.arabic li, - article ol li { - list-style-image: initial; - } - - .wy-plain-list-decimal li ul li, - .rst-content .section ol li ul li, - .rst-content ol.arabic li ul li, - article ol li ul li { - list-style-image: initial; - } - - .wy-breadcrumbs li code, - .wy-breadcrumbs li .rst-content tt, - .rst-content .wy-breadcrumbs li tt { - border-color: initial; - background-image: none; - background-color: initial; - } - - .wy-breadcrumbs li code.literal, - .wy-breadcrumbs li .rst-content tt.literal, - .rst-content .wy-breadcrumbs li tt.literal { - color: rgb(192, 186, 178); - } - - .wy-breadcrumbs-extra { - color: rgb(184, 178, 169); - } - - .wy-menu a:hover { - text-decoration-color: initial; - } - - .wy-menu-horiz li:hover { - background-image: initial; - background-color: rgba(24, 26, 27, 0.1); - } - - .wy-menu-horiz li.divide-left { - border-left-color: rgb(119, 110, 98); - } - - .wy-menu-horiz li.divide-right { - border-right-color: rgb(119, 110, 98); - } - - .wy-menu-vertical header, - .wy-menu-vertical p.caption { - color: rgb(99, 161, 201); - } - - .wy-menu-vertical li.divide-top { - border-top-color: rgb(119, 110, 98); - } - - .wy-menu-vertical li.divide-bottom { - border-bottom-color: rgb(119, 110, 98); - } - - .wy-menu-vertical li.current { - background-image: initial; - background-color: rgb(40, 43, 45); - } - - .wy-menu-vertical li.current a { - color: rgb(152, 143, 129); - border-right-color: rgb(63, 69, 71); - } - - .wy-menu-vertical li.current a:hover { - background-image: initial; - background-color: rgb(47, 51, 53); - } - - .wy-menu-vertical li code, - .wy-menu-vertical li .rst-content tt, - .rst-content .wy-menu-vertical li tt { - border-color: initial; - background-image: inherit; - background-color: inherit; - color: inherit; - } - - .wy-menu-vertical li span.toctree-expand { - color: rgb(183, 177, 168); - } - - .wy-menu-vertical li.on a, - .wy-menu-vertical li.current > a { - color: rgb(192, 186, 178); - background-image: initial; - background-color: rgb(26, 28, 29); - border-color: initial; - } - - .wy-menu-vertical li.on a:hover, - .wy-menu-vertical li.current > a:hover { - background-image: initial; - background-color: rgb(26, 28, 29); - } - - .wy-menu-vertical li.on a:hover span.toctree-expand, - .wy-menu-vertical li.current > a:hover span.toctree-expand { - color: rgb(152, 143, 129); - } - - .wy-menu-vertical li.on a span.toctree-expand, - .wy-menu-vertical li.current > a span.toctree-expand { - color: rgb(200, 195, 188); - } - - .wy-menu-vertical li.toctree-l1.current > a { - border-bottom-color: rgb(63, 69, 71); - border-top-color: rgb(63, 69, 71); - } - - .wy-menu-vertical li.toctree-l2 a, - .wy-menu-vertical li.toctree-l3 a, - .wy-menu-vertical li.toctree-l4 a { - color: rgb(192, 186, 178); - } - - .wy-menu-vertical li.toctree-l2.current > a { - background-image: initial; - background-color: rgb(54, 59, 61); - } - - .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { - background-image: initial; - background-color: rgb(54, 59, 61); - } - - .wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { - color: rgb(152, 143, 129); - } - - .wy-menu-vertical li.toctree-l2 span.toctree-expand { - color: rgb(174, 167, 156); - } - - .wy-menu-vertical li.toctree-l3.current > a { - background-image: initial; - background-color: rgb(61, 66, 69); - } - - .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { - background-image: initial; - background-color: rgb(61, 66, 69); - } - - .wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand { - color: rgb(152, 143, 129); - } - - .wy-menu-vertical li.toctree-l3 span.toctree-expand { - color: rgb(166, 158, 146); - } - - .wy-menu-vertical li.toctree-l2.current a, - .wy-menu-vertical li.toctree-l3.current a { - background-color: #363636; - } - - .wy-menu-vertical li ul li a { - color: rgb(208, 204, 198); - } - - .wy-menu-vertical a { - color: rgb(208, 204, 198); - } - - .wy-menu-vertical a:hover { - background-color: rgb(57, 62, 64); - } - - .wy-menu-vertical a:hover span.toctree-expand { - color: rgb(208, 204, 198); - } - - .wy-menu-vertical a:active { - background-color: rgb(33, 102, 148); - color: rgb(232, 230, 227); - } - - .wy-menu-vertical a:active span.toctree-expand { - color: rgb(232, 230, 227); - } - - .wy-side-nav-search { - background-color: rgb(33, 102, 148); - color: rgb(230, 228, 225); - } - - .wy-side-nav-search input[type="text"] { - border-color: rgb(35, 111, 160); - } - - .wy-side-nav-search img { - background-color: rgb(33, 102, 148); - } - - .wy-side-nav-search > a, - .wy-side-nav-search .wy-dropdown > a { - color: rgb(230, 228, 225); - } - - .wy-side-nav-search > a:hover, - .wy-side-nav-search .wy-dropdown > a:hover { - background-image: initial; - background-color: rgba(24, 26, 27, 0.1); - } - - .wy-side-nav-search > a img.logo, - .wy-side-nav-search .wy-dropdown > a img.logo { - background-image: initial; - background-color: transparent; - } - - .wy-side-nav-search > div.version { - color: rgba(232, 230, 227, 0.3); - } - - .wy-nav .wy-menu-vertical header { - color: rgb(84, 164, 217); - } - - .wy-nav .wy-menu-vertical a { - color: rgb(184, 178, 169); - } - - .wy-nav .wy-menu-vertical a:hover { - background-color: rgb(33, 102, 148); - color: rgb(232, 230, 227); - } - - .wy-body-for-nav { - background-image: initial; - background-color: rgb(24, 26, 27); - } - - .wy-nav-side { - color: rgb(169, 161, 150); - background-image: initial; - background-color: rgb(38, 41, 43); - } - - .wy-nav-top { - background-image: initial; - background-color: rgb(33, 102, 148); - color: rgb(232, 230, 227); - } - - .wy-nav-top a { - color: rgb(232, 230, 227); - } - - .wy-nav-top img { - background-color: rgb(33, 102, 148); - } - - .wy-nav-content-wrap { - background-image: initial; - background-color: rgb(26, 28, 29); - } - - .wy-body-mask { - background-image: initial; - background-color: rgba(0, 0, 0, 0.2); - } - - footer { - color: rgb(152, 143, 129); - } - - footer span.commit code, - footer span.commit .rst-content tt, - .rst-content footer span.commit tt { - background-image: none; - background-color: initial; - border-color: initial; - color: rgb(152, 143, 129); - } - - #search-results .search li { - border-bottom-color: rgb(56, 61, 63); - } - - #search-results .search li:first-child { - border-top-color: rgb(56, 61, 63); - } - - #search-results .context { - color: rgb(152, 143, 129); - } - - @media screen and (min-width: 1100px) { - .wy-nav-content-wrap { - background-image: initial; - background-color: rgba(0, 0, 0, 0.05); - } - - .wy-nav-content { - background-image: initial; - background-color: rgb(26, 28, 29); - } - } - .rst-versions { - color: rgb(230, 228, 225); - background-image: initial; - background-color: rgb(23, 24, 25); - } - - .rst-versions a { - color: rgb(84, 164, 217); - text-decoration-color: initial; - } - - .rst-versions .rst-current-version { - background-color: rgb(29, 31, 32); - color: rgb(92, 218, 145); - } - - .rst-versions .rst-current-version .fa, - .rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand, - .wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand, - .rst-versions .rst-current-version .rst-content .admonition-title, - .rst-content .rst-versions .rst-current-version .admonition-title, - .rst-versions .rst-current-version .rst-content h1 .headerlink, - .rst-content h1 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content h2 .headerlink, - .rst-content h2 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content h3 .headerlink, - .rst-content h3 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content h4 .headerlink, - .rst-content h4 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content h5 .headerlink, - .rst-content h5 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content h6 .headerlink, - .rst-content h6 .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content dl dt .headerlink, - .rst-content dl dt .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content p.caption .headerlink, - .rst-content p.caption .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content table > caption .headerlink, - .rst-content table > caption .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, - .rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, - .rst-versions .rst-current-version .rst-content tt.download span:first-child, - .rst-content tt.download .rst-versions .rst-current-version span:first-child, - .rst-versions .rst-current-version .rst-content code.download span:first-child, - .rst-content code.download .rst-versions .rst-current-version span:first-child, - .rst-versions .rst-current-version .icon { - color: rgb(230, 228, 225); - } - - .rst-versions .rst-current-version.rst-out-of-date { - background-color: rgb(162, 33, 20); - color: rgb(232, 230, 227); - } - - .rst-versions .rst-current-version.rst-active-old-version { - background-color: rgb(192, 156, 11); - color: rgb(232, 230, 227); - } - - .rst-versions .rst-other-versions { - color: rgb(152, 143, 129); - } - - .rst-versions .rst-other-versions hr { - border-right-color: initial; - border-bottom-color: initial; - border-left-color: initial; - border-top-color: rgb(119, 111, 98); - } - - .rst-versions .rst-other-versions dd a { - color: rgb(230, 228, 225); - } - - .rst-versions.rst-badge { - border-color: initial; - } - - .rst-content abbr[title] { - text-decoration-color: initial; - } - - .rst-content.style-external-links a.reference.external::after { - color: rgb(184, 178, 169); - } - - .rst-content pre.literal-block, .rst-content div[class^="highlight"] { - border-color: rgb(56, 61, 63); - } - - .rst-content pre.literal-block div[class^="highlight"], .rst-content div[class^="highlight"] div[class^="highlight"] { - border-color: initial; - } - - .rst-content .linenodiv pre { - border-right-color: rgb(54, 59, 61); - } - - .rst-content .admonition table { - border-color: rgba(140, 130, 115, 0.1); - } - - .rst-content .admonition table td, - .rst-content .admonition table th { - background-image: initial !important; - background-color: transparent !important; - border-color: rgba(140, 130, 115, 0.1) !important; - } - - .rst-content .section ol.loweralpha, - .rst-content .section ol.loweralpha li { - list-style-image: initial; - } - - .rst-content .section ol.upperalpha, - .rst-content .section ol.upperalpha li { - list-style-image: initial; - } - - .rst-content .toc-backref { - color: rgb(192, 186, 178); - } - - .rst-content .sidebar { - background-image: initial; - background-color: rgb(27, 36, 36); - border-color: rgb(56, 61, 63); - } - - .rst-content .sidebar .sidebar-title { - background-image: initial; - background-color: rgb(40, 43, 45); - } - - .rst-content .highlighted { - background-image: initial; - background-color: rgb(192, 156, 11); - } - - .rst-content table.docutils.citation, - .rst-content table.docutils.footnote { - background-image: none; - background-color: initial; - border-color: initial; - color: rgb(152, 143, 129); - } - - .rst-content table.docutils.citation td, - .rst-content table.docutils.citation tr, - .rst-content table.docutils.footnote td, - .rst-content table.docutils.footnote tr { - border-color: initial; - background-color: transparent !important; - } - - .rst-content table.docutils.citation tt, - .rst-content table.docutils.citation code, - .rst-content table.docutils.footnote tt, - .rst-content table.docutils.footnote code { - color: rgb(178, 172, 162); - } - - .rst-content table.docutils th { - border-color: rgb(56, 61, 63); - } - - .rst-content table.field-list { - border-color: initial; - } - - .rst-content table.field-list td { - border-color: initial; - } - - .rst-content tt, - .rst-content tt, - .rst-content code { - color: rgb(232, 230, 227); - } - - .rst-content tt.literal, - .rst-content tt.literal, - .rst-content code.literal { - color: rgb(233, 88, 73); - } - - .rst-content tt.xref, - a .rst-content tt, - .rst-content tt.xref, - .rst-content code.xref, - a .rst-content tt, - a .rst-content code { - color: rgb(192, 186, 178); - } - - .rst-content a tt, - .rst-content a tt, - .rst-content a code { - color: rgb(84, 164, 217); - } - - .rst-content dl:not(.docutils) dt { - background-image: initial; - background-color: rgb(32, 35, 36); - color: rgb(84, 164, 217); - border-top-color: rgb(28, 89, 128); - } - - .rst-content dl:not(.docutils) dt::before { - color: rgb(109, 178, 223); - } - - .rst-content dl:not(.docutils) dt .headerlink { - color: rgb(192, 186, 178); - } - - .rst-content dl:not(.docutils) dl dt { - border-top-color: initial; - border-right-color: initial; - border-bottom-color: initial; - border-left-color: rgb(62, 68, 70); - background-image: initial; - background-color: rgb(32, 35, 37); - color: rgb(178, 172, 162); - } - - .rst-content dl:not(.docutils) dl dt .headerlink { - color: rgb(192, 186, 178); - } - - .rst-content dl:not(.docutils) tt.descname, - .rst-content dl:not(.docutils) tt.descclassname, - .rst-content dl:not(.docutils) tt.descname, - .rst-content dl:not(.docutils) code.descname, - .rst-content dl:not(.docutils) tt.descclassname, - .rst-content dl:not(.docutils) code.descclassname { - background-color: transparent; - border-color: initial; - } - - .rst-content dl:not(.docutils) .optional { - color: rgb(232, 230, 227); - } - - .rst-content .viewcode-link, - .rst-content .viewcode-back { - color: rgb(92, 218, 145); - } - - .rst-content tt.download, - .rst-content code.download { - background-image: inherit; - background-color: inherit; - color: inherit; - border-color: inherit; - } - - .rst-content .guilabel { - border-color: rgb(27, 84, 122); - background-image: initial; - background-color: rgb(32, 35, 36); - } - - span[id*="MathJax-Span"] { - color: rgb(192, 186, 178); - } - - .highlight .hll { - background-color: rgb(82, 82, 0); - } - - .highlight { - background-image: initial; - background-color: rgb(61, 82, 0); - } - - .highlight .c { - color: rgb(119, 179, 195); - } - - .highlight .err { - border-color: rgb(179, 0, 0); - } - - .highlight .k { - color: rgb(126, 255, 163); - } - - .highlight .o { - color: rgb(168, 160, 149); - } - - .highlight .ch { - color: rgb(119, 179, 195); - } - - .highlight .cm { - color: rgb(119, 179, 195); - } - - .highlight .cp { - color: rgb(126, 255, 163); - } - - .highlight .cpf { - color: rgb(119, 179, 195); - } - - .highlight .c1 { - color: rgb(119, 179, 195); - } - - .highlight .cs { - color: rgb(119, 179, 195); - background-color: rgb(60, 0, 0); - } - - .highlight .gd { - color: rgb(255, 92, 92); - } - - .highlight .gr { - color: rgb(255, 26, 26); - } - - .highlight .gh { - color: rgb(127, 174, 255); - } - - .highlight .gi { - color: rgb(92, 255, 92); - } - - .highlight .go { - color: rgb(200, 195, 188); - } - - .highlight .gp { - color: rgb(246, 147, 68); - } - - .highlight .gu { - color: rgb(255, 114, 255); - } - - .highlight .gt { - color: rgb(71, 160, 255); - } - - .highlight .kc { - color: rgb(126, 255, 163); - } - - .highlight .kd { - color: rgb(126, 255, 163); - } - - .highlight .kn { - color: rgb(126, 255, 163); - } - - .highlight .kp { - color: rgb(126, 255, 163); - } - - .highlight .kr { - color: rgb(126, 255, 163); - } - - .highlight .kt { - color: rgb(255, 137, 103); - } - - .highlight .m { - color: rgb(125, 222, 174); - } - - .highlight .s { - color: rgb(123, 166, 202); - } - - .highlight .na { - color: rgb(123, 166, 202); - } - - .highlight .nb { - color: rgb(126, 255, 163); - } - - .highlight .nc { - color: rgb(81, 194, 242); - } - - .highlight .no { - color: rgb(103, 177, 215); - } - - .highlight .nd { - color: rgb(178, 172, 162); - } - - .highlight .ni { - color: rgb(217, 100, 73); - } - - .highlight .ne { - color: rgb(126, 255, 163); - } - - .highlight .nf { - color: rgb(131, 186, 249); - } - - .highlight .nl { - color: rgb(137, 193, 255); - } - - .highlight .nn { - color: rgb(81, 194, 242); - } - - .highlight .nt { - color: rgb(138, 191, 249); - } - - .highlight .nv { - color: rgb(190, 103, 215); - } - - .highlight .ow { - color: rgb(126, 255, 163); - } - - .highlight .w { - color: rgb(189, 183, 175); - } - - .highlight .mb { - color: rgb(125, 222, 174); - } - - .highlight .mf { - color: rgb(125, 222, 174); - } - - .highlight .mh { - color: rgb(125, 222, 174); - } - - .highlight .mi { - color: rgb(125, 222, 174); - } - - .highlight .mo { - color: rgb(125, 222, 174); - } - - .highlight .sa { - color: rgb(123, 166, 202); - } - - .highlight .sb { - color: rgb(123, 166, 202); - } - - .highlight .sc { - color: rgb(123, 166, 202); - } - - .highlight .dl { - color: rgb(123, 166, 202); - } - - .highlight .sd { - color: rgb(123, 166, 202); - } - - .highlight .s2 { - color: rgb(123, 166, 202); - } - - .highlight .se { - color: rgb(123, 166, 202); - } - - .highlight .sh { - color: rgb(123, 166, 202); - } - - .highlight .si { - color: rgb(117, 168, 209); - } - - .highlight .sx { - color: rgb(246, 147, 68); - } - - .highlight .sr { - color: rgb(133, 182, 224); - } - - .highlight .s1 { - color: rgb(123, 166, 202); - } - - .highlight .ss { - color: rgb(188, 230, 128); - } - - .highlight .bp { - color: rgb(126, 255, 163); - } - - .highlight .fm { - color: rgb(131, 186, 249); - } - - .highlight .vc { - color: rgb(190, 103, 215); - } - - .highlight .vg { - color: rgb(190, 103, 215); - } - - .highlight .vi { - color: rgb(190, 103, 215); - } - - .highlight .vm { - color: rgb(190, 103, 215); - } - - .highlight .il { - color: rgb(125, 222, 174); - } - - .rst-other-versions a { - border-color: initial; - } - - .ethical-sidebar .ethical-image-link, - .ethical-footer .ethical-image-link { - border-color: initial; - } - - .ethical-sidebar, - .ethical-footer { - background-color: rgb(34, 36, 38); - border-color: rgb(62, 68, 70); - color: rgb(226, 223, 219); - } - - .ethical-sidebar ul { - list-style-image: initial; - } - - .ethical-sidebar ul li { - background-color: rgb(5, 77, 121); - color: rgb(232, 230, 227); - } - - .ethical-sidebar a, - .ethical-sidebar a:visited, - .ethical-sidebar a:hover, - .ethical-sidebar a:active, - .ethical-footer a, - .ethical-footer a:visited, - .ethical-footer a:hover, - .ethical-footer a:active { - color: rgb(226, 223, 219); - text-decoration-color: initial !important; - border-bottom-color: initial !important; - } - - .ethical-callout a { - color: rgb(161, 153, 141) !important; - text-decoration-color: initial !important; - } - - .ethical-fixedfooter { - background-color: rgb(34, 36, 38); - border-top-color: rgb(66, 72, 74); - color: rgb(192, 186, 178); - } - - .ethical-fixedfooter .ethical-text::before { - background-color: rgb(61, 140, 64); - color: rgb(232, 230, 227); - } - - .ethical-fixedfooter .ethical-callout { - color: rgb(168, 160, 149); - } - - .ethical-fixedfooter a, - .ethical-fixedfooter a:hover, - .ethical-fixedfooter a:active, - .ethical-fixedfooter a:visited { - color: rgb(192, 186, 178); - text-decoration-color: initial; - } - - .ethical-rtd .ethical-sidebar { - color: rgb(184, 178, 169); - } - - .ethical-alabaster a.ethical-image-link { - border-color: initial !important; - } - - .ethical-dark-theme .ethical-sidebar { - background-color: rgb(58, 62, 65); - border-color: rgb(75, 81, 84); - color: rgb(193, 188, 180) !important; - } - - .ethical-dark-theme a, - .ethical-dark-theme a:visited { - color: rgb(216, 213, 208) !important; - border-bottom-color: initial !important; - } - - .ethical-dark-theme .ethical-callout a { - color: rgb(184, 178, 169) !important; - } - - .keep-us-sustainable { - border-color: rgb(87, 133, 38); - } - - .keep-us-sustainable a, - .keep-us-sustainable a:hover, - .keep-us-sustainable a:visited { - text-decoration-color: initial; - } - - .wy-body-for-nav .keep-us-sustainable { - color: rgb(184, 178, 169); - } - - .wy-body-for-nav .keep-us-sustainable a { - color: rgb(222, 219, 215); - } - - /* For black-on-white/transparent images at handbook/text-anchors.html */ - #text-anchors img { + body[data-theme="auto"] #text-anchors img { filter: invert(1) brightness(0.85) hue-rotate(-60deg); } } diff --git a/docs/resources/css/light.css b/docs/resources/css/light.css deleted file mode 100644 index 04edd7b16..000000000 --- a/docs/resources/css/light.css +++ /dev/null @@ -1,8 +0,0 @@ -@media (prefers-color-scheme: light) { - - .wy-menu-vertical li.toctree-l2.current a, - .wy-menu-vertical li.toctree-l3.current a { - background-color: #c9c9c9; - } - -} diff --git a/docs/resources/css/styles.css b/docs/resources/css/styles.css deleted file mode 100644 index 111f84085..000000000 --- a/docs/resources/css/styles.css +++ /dev/null @@ -1,8 +0,0 @@ -th p { - margin-bottom: 0; -} - -.rst-content tr .line-block { - font-size: 1rem; - margin-bottom: 0; -} diff --git a/docs/resources/js/script.js b/docs/resources/js/script.js deleted file mode 100644 index 5cb6494ea..000000000 --- a/docs/resources/js/script.js +++ /dev/null @@ -1,58 +0,0 @@ -jQuery(document).ready(function ($) { - setTimeout(function () { - var sectionID = 'base'; - var search = function ($section, $sidebarItem) { - $section.children('.section, .function, .method').each(function () { - if ($(this).hasClass('section')) { - sectionID = $(this).attr('id'); - search($(this), $sidebarItem.parent().find('[href="#'+sectionID+'"]')); - } else { - var $dt = $(this).children('dt'); - var id = $dt.attr('id'); - if (id === undefined) { - return; - } - - var $functionsUL = $sidebarItem.siblings('[data-sectionID='+sectionID+']'); - if (!$functionsUL.length) { - $functionsUL = $('