Merge branch 'master' into simplified
|
@ -27,6 +27,7 @@ python3 -m pip install coverage
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
python3 -m pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
python3 -m pip install test-image-results
|
||||||
# TODO Remove condition when numpy supports 3.10
|
# TODO Remove condition when numpy supports 3.10
|
||||||
|
|
1
.github/CONTRIBUTING.md
vendored
|
@ -18,6 +18,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
- Provide tests for any newly added code.
|
- Provide tests for any newly added code.
|
||||||
- Follow PEP 8.
|
- Follow PEP 8.
|
||||||
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
||||||
|
- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
|
1
.github/workflows/macos-install.sh
vendored
|
@ -9,6 +9,7 @@ python3 -m pip install coverage
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
python3 -m pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
python3 -m pip install test-image-results
|
||||||
|
|
||||||
|
|
13
.github/workflows/test-windows.yml
vendored
|
@ -57,8 +57,8 @@ jobs:
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python .github/workflows/system-info.py
|
run: python .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: python -m pip install wheel pytest pytest-cov
|
- name: python -m pip install wheel pytest pytest-cov pytest-timeout
|
||||||
run: python -m pip install wheel pytest pytest-cov
|
run: python -m pip install wheel pytest pytest-cov pytest-timeout
|
||||||
|
|
||||||
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
- name: Upgrade setuptools
|
- name: Upgrade setuptools
|
||||||
|
@ -110,7 +110,7 @@ jobs:
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
||||||
|
|
||||||
# for FreeType CBDT font support
|
# for FreeType CBDT/SBIX font support
|
||||||
- name: Build dependencies / libpng
|
- name: Build dependencies / libpng
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||||
|
@ -137,14 +137,11 @@ jobs:
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
|
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
|
||||||
|
|
||||||
|
# Raqm dependencies
|
||||||
- name: Build dependencies / FriBidi
|
- name: Build dependencies / FriBidi
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_fribidi.cmd"
|
run: "& winbuild\\build\\build_dep_fribidi.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / Raqm
|
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
|
||||||
run: "& winbuild\\build\\build_dep_libraqm.cmd"
|
|
||||||
|
|
||||||
# trim ~150MB x 9
|
# trim ~150MB x 9
|
||||||
- name: Optimize build cache
|
- name: Optimize build cache
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
|
@ -174,7 +171,7 @@ jobs:
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
shell: pwsh
|
shell: bash
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
|
1
.github/workflows/test.yml
vendored
|
@ -92,7 +92,6 @@ jobs:
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
|
3
.gitignore
vendored
|
@ -88,3 +88,6 @@ Tests/images/jpeg2000
|
||||||
Tests/images/msp
|
Tests/images/msp
|
||||||
Tests/images/picins
|
Tests/images/picins
|
||||||
Tests/images/sunraster
|
Tests/images/sunraster
|
||||||
|
|
||||||
|
# pyinstaller
|
||||||
|
*.spec
|
||||||
|
|
57
CHANGES.rst
|
@ -5,6 +5,63 @@ Changelog (Pillow)
|
||||||
8.2.0 (unreleased)
|
8.2.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Fixed linear_gradient and radial_gradient I and F modes #5274
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add support for reading TIFFs with PlanarConfiguration=2 #5364
|
||||||
|
[kkopachev, wiredfool, nulano]
|
||||||
|
|
||||||
|
- Deprecated categories #5351
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Dynamically link FriBiDi instead of Raqm #5062
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Allow fewer PNG palette entries than the bit depth maximum when saving #5330
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use duration from info dictionary when saving WebP #5338
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Stop flattening EXIF IFD into getexif() #4947
|
||||||
|
[radarhere, kkopachev]
|
||||||
|
|
||||||
|
- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Save ICC profile from TIFF encoderinfo #5321
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Moved RGB fix inside ImageQt class #5268
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow alpha_composite destination to be negative #5313
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ensure file is closed if it is opened by ImageQt.ImageQt #5260
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageDraw rounded_rectangle method #5208
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added IPythonViewer #5289
|
||||||
|
[radarhere, Kipkurui-mutai]
|
||||||
|
|
||||||
|
- Only draw each rectangle outline pixel once #5183
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use mmap instead of built-in Win32 mapper #5224
|
||||||
|
[radarhere, cgohlke]
|
||||||
|
|
||||||
|
- Handle PCX images with an odd stride #5214
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Only read different sizes for "Large Thumbnail" MPO frames #5168
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Added PyQt6 support #5258
|
- Added PyQt6 support #5258
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
40
Tests/fonts/DejaVuSans/LICENSE.txt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
|
||||||
|
|
||||||
|
DejaVu Fonts — License
|
||||||
|
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below)
|
||||||
|
|
||||||
|
Bitstream Vera Fonts Copyright
|
||||||
|
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||||
|
|
||||||
|
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
|
||||||
|
|
||||||
|
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
|
||||||
|
|
||||||
|
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
|
||||||
|
|
||||||
|
Arev Fonts Copyright
|
||||||
|
Original text
|
||||||
|
|
||||||
|
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||||
|
|
||||||
|
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
|
||||||
|
|
||||||
|
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
|
||||||
|
|
||||||
|
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
|
@ -15,8 +15,11 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception.
|
||||||
|
|
||||||
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
|
chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC
|
||||||
|
|
||||||
|
KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later.
|
||||||
|
|
||||||
|
FreeMono.ttf is licensed under GPLv3.
|
||||||
|
|
||||||
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/chromacheck-sbix.woff
Normal file
BIN
Tests/images/chromacheck-sbix.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/chromacheck-sbix_mask.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/ignore_frame_size.mpo
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
Tests/images/imagedraw_rectangle_translucent_outline.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
Tests/images/imagedraw_rounded_rectangle.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
Tests/images/imagedraw_rounded_rectangle_both.png
Normal file
After Width: | Height: | Size: 530 B |
BIN
Tests/images/imagedraw_rounded_rectangle_x.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
Tests/images/imagedraw_rounded_rectangle_y.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
Tests/images/odd_stride.pcx
Normal file
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
BIN
Tests/images/tiff_strip_planar_16bit_RGB.tiff
Normal file
BIN
Tests/images/tiff_strip_planar_16bit_RGBa.tiff
Normal file
BIN
Tests/images/tiff_strip_planar_lzw.tiff
Normal file
BIN
Tests/images/tiff_tiled_planar_16bit_RGB.tiff
Normal file
BIN
Tests/images/tiff_tiled_planar_16bit_RGBa.tiff
Normal file
BIN
Tests/images/tiff_tiled_planar_lzw.tiff
Normal file
48
Tests/oss-fuzz/build.sh
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/bash -eu
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
python3 setup.py build --build-base=/tmp/build install
|
||||||
|
|
||||||
|
# Build fuzzers in $OUT.
|
||||||
|
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||||
|
fuzzer_basename=$(basename -s .py $fuzzer)
|
||||||
|
fuzzer_package=${fuzzer_basename}.pkg
|
||||||
|
pyinstaller \
|
||||||
|
--add-binary /usr/local/lib/libjpeg.so.9:. \
|
||||||
|
--add-binary /usr/local/lib/libfreetype.so.6:. \
|
||||||
|
--add-binary /usr/local/lib/liblcms2.so.2:. \
|
||||||
|
--add-binary /usr/local/lib/libopenjp2.so.7:. \
|
||||||
|
--add-binary /usr/local/lib/libpng16.so.16:. \
|
||||||
|
--add-binary /usr/local/lib/libtiff.so.5:. \
|
||||||
|
--add-binary /usr/local/lib/libwebp.so.7:. \
|
||||||
|
--add-binary /usr/local/lib/libwebpdemux.so.2:. \
|
||||||
|
--add-binary /usr/local/lib/libwebpmux.so.3:. \
|
||||||
|
--add-binary /usr/local/lib/libxcb.so.1:. \
|
||||||
|
--distpath $OUT --onefile --name $fuzzer_package $fuzzer
|
||||||
|
|
||||||
|
# Create execution wrapper.
|
||||||
|
echo "#!/bin/sh
|
||||||
|
# LLVMFuzzerTestOneInput for fuzzer detection.
|
||||||
|
this_dir=\$(dirname \"\$0\")
|
||||||
|
LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \
|
||||||
|
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \
|
||||||
|
\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename
|
||||||
|
chmod u+x $OUT/$fuzzer_basename
|
||||||
|
done
|
||||||
|
|
||||||
|
find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@
|
||||||
|
find Tests/fonts -print | zip -q $OUT/fuzz_font_seed_corpus.zip -@
|
33
Tests/oss-fuzz/build_dictionaries.sh
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash -eu
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Generate image dictionaries here for each of the fuzzers and put them in the
|
||||||
|
# $OUT directory, named for the fuzzer
|
||||||
|
|
||||||
|
git clone --depth 1 https://github.com/google/fuzzing
|
||||||
|
cat fuzzing/dictionaries/bmp.dict \
|
||||||
|
fuzzing/dictionaries/dds.dict \
|
||||||
|
fuzzing/dictionaries/gif.dict \
|
||||||
|
fuzzing/dictionaries/icns.dict \
|
||||||
|
fuzzing/dictionaries/jpeg.dict \
|
||||||
|
fuzzing/dictionaries/jpeg2000.dict \
|
||||||
|
fuzzing/dictionaries/pbm.dict \
|
||||||
|
fuzzing/dictionaries/png.dict \
|
||||||
|
fuzzing/dictionaries/psd.dict \
|
||||||
|
fuzzing/dictionaries/tiff.dict \
|
||||||
|
fuzzing/dictionaries/webp.dict \
|
||||||
|
> $OUT/fuzz_pillow.dict
|
40
Tests/oss-fuzz/fuzz_font.py
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import atheris_no_libfuzzer as atheris
|
||||||
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
|
def TestOneInput(data):
|
||||||
|
try:
|
||||||
|
fuzzers.fuzz_font(data)
|
||||||
|
except Exception:
|
||||||
|
# We're catching all exceptions because Pillow's exceptions are
|
||||||
|
# directly inheriting from Exception.
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fuzzers.enable_decompressionbomb_error()
|
||||||
|
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
||||||
|
atheris.Fuzz()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -14,21 +14,15 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import io
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
|
|
||||||
import atheris_no_libfuzzer as atheris
|
import atheris_no_libfuzzer as atheris
|
||||||
|
import fuzzers
|
||||||
from PIL import Image, ImageFile, ImageFilter
|
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data):
|
||||||
try:
|
try:
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
fuzzers.fuzz_image(data)
|
||||||
im.rotate(45)
|
|
||||||
im.filter(ImageFilter.DETAIL)
|
|
||||||
im.save(io.BytesIO(), "BMP")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# We're catching all exceptions because Pillow's exceptions are
|
# We're catching all exceptions because Pillow's exceptions are
|
||||||
# directly inheriting from Exception.
|
# directly inheriting from Exception.
|
||||||
|
@ -37,9 +31,7 @@ def TestOneInput(data):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
fuzzers.enable_decompressionbomb_error()
|
||||||
warnings.filterwarnings("ignore")
|
|
||||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
|
||||||
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
||||||
|
|
36
Tests/oss-fuzz/fuzzers.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import io
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||||
|
|
||||||
|
|
||||||
|
def enable_decompressionbomb_error():
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
|
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||||
|
|
||||||
|
|
||||||
|
def fuzz_image(data):
|
||||||
|
# This will fail on some images in the corpus, as we have many
|
||||||
|
# invalid images in the test suite.
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
im.rotate(45)
|
||||||
|
im.filter(ImageFilter.DETAIL)
|
||||||
|
im.save(io.BytesIO(), "BMP")
|
||||||
|
|
||||||
|
|
||||||
|
def fuzz_font(data):
|
||||||
|
wrapper = io.BytesIO(data)
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(wrapper)
|
||||||
|
except OSError:
|
||||||
|
# Catch pcf/pilfonts/random garbage here. They return
|
||||||
|
# different font objects.
|
||||||
|
return
|
||||||
|
|
||||||
|
font.getsize_multiline("ABC\nAaaa")
|
||||||
|
font.getmask("test text")
|
||||||
|
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2)
|
||||||
|
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
53
Tests/oss-fuzz/test_fuzzers.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fuzzers
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
if sys.platform.startswith("win32"):
|
||||||
|
pytest.skip("Fuzzer is linux only", allow_module_level=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path",
|
||||||
|
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
||||||
|
)
|
||||||
|
def test_fuzz_images(path):
|
||||||
|
fuzzers.enable_decompressionbomb_error()
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
fuzzers.fuzz_image(f.read())
|
||||||
|
assert True
|
||||||
|
except (
|
||||||
|
OSError,
|
||||||
|
SyntaxError,
|
||||||
|
MemoryError,
|
||||||
|
ValueError,
|
||||||
|
NotImplementedError,
|
||||||
|
OverflowError,
|
||||||
|
):
|
||||||
|
# Known exceptions that are through from Pillow
|
||||||
|
assert True
|
||||||
|
except (
|
||||||
|
Image.DecompressionBombError,
|
||||||
|
Image.DecompressionBombWarning,
|
||||||
|
Image.UnidentifiedImageError,
|
||||||
|
):
|
||||||
|
# Known Image.* exceptions
|
||||||
|
assert True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||||
|
)
|
||||||
|
def test_fuzz_fonts(path):
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
try:
|
||||||
|
fuzzers.fuzz_font(f.read())
|
||||||
|
except (Image.DecompressionBombError, Image.DecompressionBombWarning):
|
||||||
|
pass
|
||||||
|
assert True
|
|
@ -143,8 +143,8 @@ def test_not_an_icns_file():
|
||||||
|
|
||||||
|
|
||||||
def test_icns_decompression_bomb():
|
def test_icns_decompression_bomb():
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with Image.open(
|
||||||
im = Image.open(
|
|
||||||
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
|
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
|
||||||
)
|
) as im:
|
||||||
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -264,11 +264,11 @@ class TestFileJpeg:
|
||||||
assert exif[0x0112] == Image.TRANSVERSE
|
assert exif[0x0112] == Image.TRANSVERSE
|
||||||
|
|
||||||
# Assert that the GPS IFD is present and empty
|
# Assert that the GPS IFD is present and empty
|
||||||
assert exif[0x8825] == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
transposed = ImageOps.exif_transpose(im)
|
transposed = ImageOps.exif_transpose(im)
|
||||||
exif = transposed.getexif()
|
exif = transposed.getexif()
|
||||||
assert exif[0x8825] == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
# Assert that it was transposed
|
# Assert that it was transposed
|
||||||
assert 0x0112 not in exif
|
assert 0x0112 not in exif
|
||||||
|
|
|
@ -17,7 +17,6 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
is_big_endian,
|
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -471,6 +470,14 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert reloaded.info["compression"] == "jpeg"
|
assert reloaded.info["compression"] == "jpeg"
|
||||||
|
|
||||||
|
def test_tiff_deflate_compression(self, tmp_path):
|
||||||
|
im = hopper("RGB")
|
||||||
|
out = str(tmp_path / "temp.tif")
|
||||||
|
im.save(out, compression="tiff_deflate")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.info["compression"] == "tiff_adobe_deflate"
|
||||||
|
|
||||||
def test_quality(self, tmp_path):
|
def test_quality(self, tmp_path):
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = str(tmp_path / "temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
@ -816,14 +823,12 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
||||||
|
|
||||||
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
||||||
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
||||||
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
||||||
|
|
||||||
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
||||||
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
||||||
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
|
@ -835,20 +840,57 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
||||||
|
|
||||||
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
||||||
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
||||||
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
||||||
|
|
||||||
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
||||||
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
||||||
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
||||||
|
|
||||||
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
def test_strip_planar_rgb(self):
|
||||||
|
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
||||||
|
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
|
||||||
|
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
|
||||||
|
with Image.open(infile) as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
|
def test_tiled_planar_rgb(self):
|
||||||
|
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
||||||
|
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
|
||||||
|
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
|
||||||
|
with Image.open(infile) as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
|
def test_tiled_planar_16bit_RGB(self):
|
||||||
|
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
||||||
|
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
|
||||||
|
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
||||||
|
|
||||||
|
def test_strip_planar_16bit_RGB(self):
|
||||||
|
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
||||||
|
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
|
||||||
|
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
||||||
|
|
||||||
|
def test_tiled_planar_16bit_RGBa(self):
|
||||||
|
# gdal_translate -co TILED=yes \
|
||||||
|
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
|
||||||
|
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
|
||||||
|
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
||||||
|
|
||||||
|
def test_strip_planar_16bit_RGBa(self):
|
||||||
|
# gdal_translate -co TILED=no \
|
||||||
|
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
|
||||||
|
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
|
||||||
|
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
||||||
|
|
||||||
def test_old_style_jpeg(self):
|
def test_old_style_jpeg(self):
|
||||||
infile = "Tests/images/old-style-jpeg-compression.tif"
|
infile = "Tests/images/old-style-jpeg-compression.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
|
|
|
@ -89,6 +89,20 @@ def test_frame_size():
|
||||||
assert im.size == (680, 480)
|
assert im.size == (680, 480)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ignore_frame_size():
|
||||||
|
# Ignore the different size of the second frame
|
||||||
|
# since this is not a "Large Thumbnail" image
|
||||||
|
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
|
||||||
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert (
|
||||||
|
im.mpinfo[0xB002][1]["Attribute"]["MPType"]
|
||||||
|
== "Multi-Frame Image: (Disparity)"
|
||||||
|
)
|
||||||
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
|
|
||||||
def test_parallax():
|
def test_parallax():
|
||||||
# Nintendo
|
# Nintendo
|
||||||
with Image.open("Tests/images/sugarshack.mpo") as im:
|
with Image.open("Tests/images/sugarshack.mpo") as im:
|
||||||
|
@ -132,7 +146,7 @@ def test_mp_attribute():
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
frameNumber = 0
|
frameNumber = 0
|
||||||
for mpentry in mpinfo[45058]:
|
for mpentry in mpinfo[0xB002]:
|
||||||
mpattr = mpentry["Attribute"]
|
mpattr = mpentry["Attribute"]
|
||||||
if frameNumber:
|
if frameNumber:
|
||||||
assert not mpattr["RepresentativeImageFlag"]
|
assert not mpattr["RepresentativeImageFlag"]
|
||||||
|
|
|
@ -44,6 +44,14 @@ def test_odd(tmp_path):
|
||||||
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
|
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_odd_read():
|
||||||
|
# Reading an image with an odd stride, making it malformed
|
||||||
|
with Image.open("Tests/images/odd_stride.pcx") as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
assert im.size == (371, 150)
|
||||||
|
|
||||||
|
|
||||||
def test_pil184():
|
def test_pil184():
|
||||||
# Check reading of files where xmin/xmax is not zero.
|
# Check reading of files where xmin/xmax is not zero.
|
||||||
|
|
||||||
|
|
|
@ -517,6 +517,8 @@ class TestFilePng:
|
||||||
|
|
||||||
def test_discard_icc_profile(self):
|
def test_discard_icc_profile(self):
|
||||||
with Image.open("Tests/images/icc_profile.png") as im:
|
with Image.open("Tests/images/icc_profile.png") as im:
|
||||||
|
assert "icc_profile" in im.info
|
||||||
|
|
||||||
im = roundtrip(im, icc_profile=None)
|
im = roundtrip(im, icc_profile=None)
|
||||||
assert "icc_profile" not in im.info
|
assert "icc_profile" not in im.info
|
||||||
|
|
||||||
|
@ -623,6 +625,25 @@ class TestFilePng:
|
||||||
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
|
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
|
||||||
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
|
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
|
||||||
|
|
||||||
|
def test_specify_bits(self, tmp_path):
|
||||||
|
im = hopper("P")
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.png")
|
||||||
|
im.save(out, bits=4)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert len(reloaded.png.im_palette[1]) == 48
|
||||||
|
|
||||||
|
def test_plte_length(self, tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
im.putpalette((1, 1, 1))
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.png")
|
||||||
|
im.save(str(tmp_path / "temp.png"))
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert len(reloaded.png.im_palette[1]) == 3
|
||||||
|
|
||||||
def test_exif(self):
|
def test_exif(self):
|
||||||
# With an EXIF chunk
|
# With an EXIF chunk
|
||||||
with Image.open("Tests/images/exif.png") as im:
|
with Image.open("Tests/images/exif.png") as im:
|
||||||
|
|
|
@ -568,6 +568,28 @@ class TestFileTiff:
|
||||||
with Image.open(tmpfile) as reloaded:
|
with Image.open(tmpfile) as reloaded:
|
||||||
assert b"Dummy value" == reloaded.info["icc_profile"]
|
assert b"Dummy value" == reloaded.info["icc_profile"]
|
||||||
|
|
||||||
|
def test_save_icc_profile(self, tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
assert "icc_profile" not in im.info
|
||||||
|
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
icc_profile = b"Dummy value"
|
||||||
|
im.save(outfile, icc_profile=icc_profile)
|
||||||
|
|
||||||
|
with Image.open(outfile) as reloaded:
|
||||||
|
assert reloaded.info["icc_profile"] == icc_profile
|
||||||
|
|
||||||
|
def test_discard_icc_profile(self, tmp_path):
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/icc_profile.png") as im:
|
||||||
|
assert "icc_profile" in im.info
|
||||||
|
|
||||||
|
im.save(outfile, icc_profile=None)
|
||||||
|
|
||||||
|
with Image.open(outfile) as reloaded:
|
||||||
|
assert "icc_profile" not in reloaded.info
|
||||||
|
|
||||||
def test_close_on_load_exclusive(self, tmp_path):
|
def test_close_on_load_exclusive(self, tmp_path):
|
||||||
# similar to test_fd_leak, but runs on unixlike os
|
# similar to test_fd_leak, but runs on unixlike os
|
||||||
tmpfile = str(tmp_path / "temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
|
@ -176,3 +176,16 @@ class TestFileWebp:
|
||||||
[abs(original_value[i] - reread_value[i]) for i in range(0, 3)]
|
[abs(original_value[i] - reread_value[i]) for i in range(0, 3)]
|
||||||
)
|
)
|
||||||
assert difference < 5
|
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:
|
||||||
|
assert im.info["duration"] == 1000
|
||||||
|
|
||||||
|
out_webp = str(tmp_path / "temp.webp")
|
||||||
|
im.save(out_webp, save_all=True)
|
||||||
|
|
||||||
|
with Image.open(out_webp) as reloaded:
|
||||||
|
reloaded.load()
|
||||||
|
assert reloaded.info["duration"] == 1000
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -344,6 +345,12 @@ class TestImage:
|
||||||
assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63)))
|
assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63)))
|
||||||
assert offset.size == (128, 128)
|
assert offset.size == (128, 128)
|
||||||
|
|
||||||
|
# with negative offset
|
||||||
|
offset = src.copy()
|
||||||
|
offset.alpha_composite(over, (-64, -64))
|
||||||
|
assert_image_equal(offset.crop((0, 0, 63, 63)), target.crop((64, 64, 127, 127)))
|
||||||
|
assert offset.size == (128, 128)
|
||||||
|
|
||||||
# offset and crop
|
# offset and crop
|
||||||
box = src.copy()
|
box = src.copy()
|
||||||
box.alpha_composite(over, (64, 64), (0, 0, 32, 32))
|
box.alpha_composite(over, (64, 64), (0, 0, 32, 32))
|
||||||
|
@ -367,8 +374,6 @@ class TestImage:
|
||||||
source.alpha_composite(over, 0)
|
source.alpha_composite(over, 0)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
source.alpha_composite(over, (0, 0), 0)
|
source.alpha_composite(over, (0, 0), 0)
|
||||||
with pytest.raises(ValueError):
|
|
||||||
source.alpha_composite(over, (0, -1))
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
source.alpha_composite(over, (0, 0), (0, -1))
|
source.alpha_composite(over, (0, 0), (0, -1))
|
||||||
|
|
||||||
|
@ -519,7 +524,7 @@ class TestImage:
|
||||||
|
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/linear_gradient.png"
|
target_file = "Tests/images/linear_gradient.png"
|
||||||
for mode in ["L", "P"]:
|
for mode in ["L", "P", "I", "F"]:
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.linear_gradient(mode)
|
im = Image.linear_gradient(mode)
|
||||||
|
@ -545,7 +550,7 @@ class TestImage:
|
||||||
|
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/radial_gradient.png"
|
target_file = "Tests/images/radial_gradient.png"
|
||||||
for mode in ["L", "P"]:
|
for mode in ["L", "P", "I", "F"]:
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.radial_gradient(mode)
|
im = Image.radial_gradient(mode)
|
||||||
|
@ -663,43 +668,43 @@ class TestImage:
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
assert 258 not in exif
|
assert 258 not in exif
|
||||||
assert 274 in exif
|
assert 274 in exif
|
||||||
assert 40960 in exif
|
assert 282 in exif
|
||||||
assert exif[40963] == 450
|
assert exif[296] == 2
|
||||||
assert exif[11] == "gThumb 3.0.1"
|
assert exif[11] == "gThumb 3.0.1"
|
||||||
|
|
||||||
out = str(tmp_path / "temp.jpg")
|
out = str(tmp_path / "temp.jpg")
|
||||||
exif[258] = 8
|
exif[258] = 8
|
||||||
del exif[274]
|
del exif[274]
|
||||||
del exif[40960]
|
del exif[282]
|
||||||
exif[40963] = 455
|
exif[296] = 455
|
||||||
exif[11] = "Pillow test"
|
exif[11] = "Pillow test"
|
||||||
im.save(out, exif=exif)
|
im.save(out, exif=exif)
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
reloaded_exif = reloaded.getexif()
|
reloaded_exif = reloaded.getexif()
|
||||||
assert reloaded_exif[258] == 8
|
assert reloaded_exif[258] == 8
|
||||||
assert 274 not in reloaded_exif
|
assert 274 not in reloaded_exif
|
||||||
assert 40960 not in reloaded_exif
|
assert 282 not in reloaded_exif
|
||||||
assert reloaded_exif[40963] == 455
|
assert reloaded_exif[296] == 455
|
||||||
assert reloaded_exif[11] == "Pillow test"
|
assert reloaded_exif[11] == "Pillow test"
|
||||||
|
|
||||||
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian
|
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
assert 258 not in exif
|
assert 258 not in exif
|
||||||
assert 40962 in exif
|
assert 306 in exif
|
||||||
assert exif[40963] == 200
|
assert exif[274] == 1
|
||||||
assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
|
assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
|
||||||
|
|
||||||
out = str(tmp_path / "temp.jpg")
|
out = str(tmp_path / "temp.jpg")
|
||||||
exif[258] = 8
|
exif[258] = 8
|
||||||
del exif[34665]
|
del exif[306]
|
||||||
exif[40963] = 455
|
exif[274] = 455
|
||||||
exif[305] = "Pillow test"
|
exif[305] = "Pillow test"
|
||||||
im.save(out, exif=exif)
|
im.save(out, exif=exif)
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
reloaded_exif = reloaded.getexif()
|
reloaded_exif = reloaded.getexif()
|
||||||
assert reloaded_exif[258] == 8
|
assert reloaded_exif[258] == 8
|
||||||
assert 34665 not in reloaded_exif
|
assert 306 not in reloaded_exif
|
||||||
assert reloaded_exif[40963] == 455
|
assert reloaded_exif[274] == 455
|
||||||
assert reloaded_exif[305] == "Pillow test"
|
assert reloaded_exif[305] == "Pillow test"
|
||||||
|
|
||||||
@skip_unless_feature("webp")
|
@skip_unless_feature("webp")
|
||||||
|
@ -752,6 +757,33 @@ class TestImage:
|
||||||
4098: 1704,
|
4098: 1704,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloaded_exif = Image.Exif()
|
||||||
|
reloaded_exif.load(exif.tobytes())
|
||||||
|
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
|
||||||
|
|
||||||
|
def test_exif_ifd(self):
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
exif = im.getexif()
|
||||||
|
del exif.get_ifd(0x8769)[0xA005]
|
||||||
|
|
||||||
|
reloaded_exif = Image.Exif()
|
||||||
|
reloaded_exif.load(exif.tobytes())
|
||||||
|
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 7), reason="Python 3.7 or greater required"
|
||||||
|
)
|
||||||
|
def test_categories_deprecation(self):
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert hopper().category == 0
|
||||||
|
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert Image.NORMAL == 0
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert Image.SEQUENCE == 1
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert Image.CONTAINER == 2
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_module",
|
"test_module",
|
||||||
[PIL, Image],
|
[PIL, Image],
|
||||||
|
|
|
@ -143,6 +143,41 @@ class TestImageTransform:
|
||||||
|
|
||||||
self._test_alpha_premult(op)
|
self._test_alpha_premult(op)
|
||||||
|
|
||||||
|
def _test_nearest(self, op, mode):
|
||||||
|
# create white image with half transparent,
|
||||||
|
# do op,
|
||||||
|
# the image should remain white with half transparent
|
||||||
|
transparent, opaque = {
|
||||||
|
"RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)),
|
||||||
|
"LA": ((255, 0), (255, 255)),
|
||||||
|
}[mode]
|
||||||
|
im = Image.new(mode, (10, 10), transparent)
|
||||||
|
im2 = Image.new(mode, (5, 10), opaque)
|
||||||
|
im.paste(im2, (0, 0))
|
||||||
|
|
||||||
|
im = op(im, (40, 10))
|
||||||
|
|
||||||
|
colors = im.getcolors()
|
||||||
|
assert colors == [
|
||||||
|
(20 * 10, opaque),
|
||||||
|
(20 * 10, transparent),
|
||||||
|
]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("RGBA", "LA"))
|
||||||
|
def test_nearest_resize(self, mode):
|
||||||
|
def op(im, sz):
|
||||||
|
return im.resize(sz, Image.NEAREST)
|
||||||
|
|
||||||
|
self._test_nearest(op, mode)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("RGBA", "LA"))
|
||||||
|
def test_nearest_transform(self, mode):
|
||||||
|
def op(im, sz):
|
||||||
|
(w, h) = im.size
|
||||||
|
return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.NEAREST)
|
||||||
|
|
||||||
|
self._test_nearest(op, mode)
|
||||||
|
|
||||||
def test_blank_fill(self):
|
def test_blank_fill(self):
|
||||||
# attempting to hit
|
# attempting to hit
|
||||||
# https://github.com/python-pillow/Pillow/issues/254 reported
|
# https://github.com/python-pillow/Pillow/issues/254 reported
|
||||||
|
|
|
@ -692,6 +692,72 @@ def test_rectangle_I16():
|
||||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rectangle_translucent_outline():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im, "RGBA")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rectangle(BBOX1, fill="black", outline=(0, 255, 0, 127), width=5)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal_tofile(
|
||||||
|
im, "Tests/images/imagedraw_rectangle_translucent_outline.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"xy",
|
||||||
|
[(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],
|
||||||
|
)
|
||||||
|
def test_rounded_rectangle(xy):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (200, 200))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rounded_rectangle(xy, 30, fill="red", outline="green", width=5)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rounded_rectangle_zero_radius():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"xy, suffix",
|
||||||
|
[
|
||||||
|
((20, 10, 80, 90), "x"),
|
||||||
|
((10, 20, 90, 80), "y"),
|
||||||
|
((20, 20, 80, 80), "both"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_rounded_rectangle_translucent(xy, suffix):
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im, "RGBA")
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rounded_rectangle(
|
||||||
|
xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal_tofile(
|
||||||
|
im, "Tests/images/imagedraw_rounded_rectangle_" + suffix + ".png"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_floodfill():
|
def test_floodfill():
|
||||||
red = ImageColor.getrgb("red")
|
red = ImageColor.getrgb("red")
|
||||||
|
|
||||||
|
@ -790,20 +856,19 @@ def create_base_image_draw(
|
||||||
|
|
||||||
|
|
||||||
def test_square():
|
def test_square():
|
||||||
with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected:
|
expected = os.path.join(IMAGES_PATH, "square.png")
|
||||||
expected.load()
|
|
||||||
img, draw = create_base_image_draw((10, 10))
|
img, draw = create_base_image_draw((10, 10))
|
||||||
draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)
|
draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK)
|
||||||
assert_image_equal(img, expected, "square as normal polygon failed")
|
assert_image_equal_tofile(img, expected, "square as normal polygon failed")
|
||||||
img, draw = create_base_image_draw((10, 10))
|
img, draw = create_base_image_draw((10, 10))
|
||||||
draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)
|
draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK)
|
||||||
assert_image_equal(img, expected, "square as inverted polygon failed")
|
assert_image_equal_tofile(img, expected, "square as inverted polygon failed")
|
||||||
img, draw = create_base_image_draw((10, 10))
|
img, draw = create_base_image_draw((10, 10))
|
||||||
draw.rectangle((2, 2, 7, 7), BLACK)
|
draw.rectangle((2, 2, 7, 7), BLACK)
|
||||||
assert_image_equal(img, expected, "square as normal rectangle failed")
|
assert_image_equal_tofile(img, expected, "square as normal rectangle failed")
|
||||||
img, draw = create_base_image_draw((10, 10))
|
img, draw = create_base_image_draw((10, 10))
|
||||||
draw.rectangle((7, 7, 2, 2), BLACK)
|
draw.rectangle((7, 7, 2, 2), BLACK)
|
||||||
assert_image_equal(img, expected, "square as inverted rectangle failed")
|
assert_image_equal_tofile(img, expected, "square as inverted rectangle failed")
|
||||||
|
|
||||||
|
|
||||||
def test_triangle_right():
|
def test_triangle_right():
|
||||||
|
@ -830,16 +895,16 @@ def test_line_horizontal():
|
||||||
os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"),
|
os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"),
|
||||||
"line straight horizontal inverted 2px wide failed",
|
"line straight horizontal inverted 2px wide failed",
|
||||||
)
|
)
|
||||||
with Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) as expected:
|
|
||||||
expected.load()
|
expected = os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 14, 5), BLACK, 3)
|
draw.line((5, 5, 14, 5), BLACK, 3)
|
||||||
assert_image_equal(
|
assert_image_equal_tofile(
|
||||||
img, expected, "line straight horizontal normal 3px wide failed"
|
img, expected, "line straight horizontal normal 3px wide failed"
|
||||||
)
|
)
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((14, 5, 5, 5), BLACK, 3)
|
draw.line((14, 5, 5, 5), BLACK, 3)
|
||||||
assert_image_equal(
|
assert_image_equal_tofile(
|
||||||
img, expected, "line straight horizontal inverted 3px wide failed"
|
img, expected, "line straight horizontal inverted 3px wide failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -879,18 +944,19 @@ def test_line_vertical():
|
||||||
os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"),
|
os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"),
|
||||||
"line straight vertical inverted 2px wide failed",
|
"line straight vertical inverted 2px wide failed",
|
||||||
)
|
)
|
||||||
with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) as expected:
|
|
||||||
expected.load()
|
expected = os.path.join(IMAGES_PATH, "line_vertical_w3px.png")
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 5, 14), BLACK, 3)
|
draw.line((5, 5, 5, 14), BLACK, 3)
|
||||||
assert_image_equal(
|
assert_image_equal_tofile(
|
||||||
img, expected, "line straight vertical normal 3px wide failed"
|
img, expected, "line straight vertical normal 3px wide failed"
|
||||||
)
|
)
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((5, 14, 5, 5), BLACK, 3)
|
draw.line((5, 14, 5, 5), BLACK, 3)
|
||||||
assert_image_equal(
|
assert_image_equal_tofile(
|
||||||
img, expected, "line straight vertical inverted 3px wide failed"
|
img, expected, "line straight vertical inverted 3px wide failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
img, draw = create_base_image_draw((110, 200))
|
img, draw = create_base_image_draw((110, 200))
|
||||||
draw.line((55, 5, 55, 195), BLACK, 101)
|
draw.line((55, 5, 55, 195), BLACK, 101)
|
||||||
assert_image_equal_tofile(
|
assert_image_equal_tofile(
|
||||||
|
@ -909,26 +975,25 @@ def test_line_vertical():
|
||||||
|
|
||||||
|
|
||||||
def test_line_oblique_45():
|
def test_line_oblique_45():
|
||||||
with Image.open(
|
expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png")
|
||||||
os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png")
|
|
||||||
) as expected:
|
|
||||||
expected.load()
|
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 14, 14), BLACK, 3)
|
draw.line((5, 5, 14, 14), BLACK, 3)
|
||||||
assert_image_equal(img, expected, "line oblique 45 normal 3px wide A failed")
|
assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide A failed")
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((14, 14, 5, 5), BLACK, 3)
|
draw.line((14, 14, 5, 5), BLACK, 3)
|
||||||
assert_image_equal(img, expected, "line oblique 45 inverted 3px wide A failed")
|
assert_image_equal_tofile(
|
||||||
with Image.open(
|
img, expected, "line oblique 45 inverted 3px wide A failed"
|
||||||
os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png")
|
)
|
||||||
) as expected:
|
|
||||||
expected.load()
|
expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png")
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((14, 5, 5, 14), BLACK, 3)
|
draw.line((14, 5, 5, 14), BLACK, 3)
|
||||||
assert_image_equal(img, expected, "line oblique 45 normal 3px wide B failed")
|
assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide B failed")
|
||||||
img, draw = create_base_image_draw((20, 20))
|
img, draw = create_base_image_draw((20, 20))
|
||||||
draw.line((5, 14, 14, 5), BLACK, 3)
|
draw.line((5, 14, 14, 5), BLACK, 3)
|
||||||
assert_image_equal(img, expected, "line oblique 45 inverted 3px wide B failed")
|
assert_image_equal_tofile(
|
||||||
|
img, expected, "line oblique 45 inverted 3px wide B failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_wide_line_dot():
|
def test_wide_line_dot():
|
||||||
|
|
|
@ -51,7 +51,7 @@ class TestImageFont:
|
||||||
ttf_copy = ttf.font_variant(size=FONT_SIZE + 1)
|
ttf_copy = ttf.font_variant(size=FONT_SIZE + 1)
|
||||||
assert ttf_copy.size == FONT_SIZE + 1
|
assert ttf_copy.size == FONT_SIZE + 1
|
||||||
|
|
||||||
second_font_path = "Tests/fonts/DejaVuSans.ttf"
|
second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
|
||||||
ttf_copy = ttf.font_variant(font=second_font_path)
|
ttf_copy = ttf.font_variant(font=second_font_path)
|
||||||
assert ttf_copy.path == second_font_path
|
assert ttf_copy.path == second_font_path
|
||||||
|
|
||||||
|
@ -153,8 +153,8 @@ class TestImageFont:
|
||||||
("text", "L", "FreeMono.ttf", 15, 36, 36),
|
("text", "L", "FreeMono.ttf", 15, 36, 36),
|
||||||
("text", "1", "FreeMono.ttf", 15, 36, 36),
|
("text", "1", "FreeMono.ttf", 15, 36, 36),
|
||||||
# issue 4177
|
# issue 4177
|
||||||
("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875),
|
("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875),
|
||||||
("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875),
|
("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875),
|
||||||
# test 'l' not including extra margin
|
# test 'l' not including extra margin
|
||||||
# using exact value 2047 / 64 for raqm, checked with debugger
|
# using exact value 2047 / 64 for raqm, checked with debugger
|
||||||
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375),
|
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375),
|
||||||
|
@ -835,7 +835,7 @@ class TestImageFont:
|
||||||
layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE]
|
layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE]
|
||||||
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf",
|
f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf",
|
||||||
24,
|
24,
|
||||||
layout_engine=self.LAYOUT_ENGINE,
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
)
|
)
|
||||||
|
@ -869,12 +869,12 @@ class TestImageFont:
|
||||||
im = Image.new("RGB", (150, 150), "white")
|
im = Image.new("RGB", (150, 150), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
d.text((10, 10), "\U0001f469", embedded_color=True, font=font)
|
d.text((10, 10), "\U0001f469", font=font, embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2)
|
assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2)
|
||||||
except IOError as e:
|
except IOError as e: # pragma: no cover
|
||||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
pytest.skip("freetype compiled without libpng or unsupported")
|
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.5.0")
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
def test_cbdt_mask(self):
|
def test_cbdt_mask(self):
|
||||||
|
@ -893,9 +893,47 @@ class TestImageFont:
|
||||||
assert_image_similar_tofile(
|
assert_image_similar_tofile(
|
||||||
im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
|
im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
|
||||||
)
|
)
|
||||||
except IOError as e:
|
except IOError as e: # pragma: no cover
|
||||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
pytest.skip("freetype compiled without libpng or unsupported")
|
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.5.1")
|
||||||
|
def test_sbix(self):
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/chromacheck-sbix.woff",
|
||||||
|
size=300,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (400, 400), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((50, 50), "\uE901", font=font, embedded_color=True)
|
||||||
|
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
|
||||||
|
except IOError as e: # pragma: no cover
|
||||||
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
|
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.5.1")
|
||||||
|
def test_sbix_mask(self):
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/chromacheck-sbix.woff",
|
||||||
|
size=300,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (400, 400), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((50, 50), "\uE901", (100, 0, 0), font=font)
|
||||||
|
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
|
||||||
|
except IOError as e: # pragma: no cover
|
||||||
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
|
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
def test_colr(self):
|
def test_colr(self):
|
||||||
|
@ -908,7 +946,7 @@ class TestImageFont:
|
||||||
im = Image.new("RGB", (300, 75), "white")
|
im = Image.new("RGB", (300, 75), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
d.text((15, 5), "Bungee", embedded_color=True, font=font)
|
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
|
||||||
|
|
||||||
|
@ -940,7 +978,9 @@ def test_render_mono_size():
|
||||||
im = Image.new("P", (100, 30), "white")
|
im = Image.new("P", (100, 30), "white")
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
ttf = ImageFont.truetype(
|
ttf = ImageFont.truetype(
|
||||||
"Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC
|
"Tests/fonts/DejaVuSans/DejaVuSans.ttf",
|
||||||
|
18,
|
||||||
|
layout_engine=ImageFont.LAYOUT_BASIC,
|
||||||
)
|
)
|
||||||
|
|
||||||
draw.text((10, 10), "r" * 10, "black", ttf)
|
draw.text((10, 10), "r" * 10, "black", ttf)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from .helper import (
|
||||||
)
|
)
|
||||||
|
|
||||||
FONT_SIZE = 20
|
FONT_SIZE = 20
|
||||||
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
|
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
|
||||||
|
|
||||||
pytestmark = skip_unless_feature("raqm")
|
pytestmark = skip_unless_feature("raqm")
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(
|
||||||
|
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||||
|
)
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import qRgba
|
from PIL.ImageQt import qRgba
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
|
||||||
def test_rgb():
|
def test_rgb():
|
||||||
# from https://doc.qt.io/archives/qt-4.8/qcolor.html
|
# from https://doc.qt.io/archives/qt-4.8/qcolor.html
|
||||||
# typedef QRgb
|
# typedef QRgb
|
||||||
|
@ -38,7 +41,13 @@ def test_rgb():
|
||||||
checkrgb(0, 0, 255)
|
checkrgb(0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
|
||||||
def test_image():
|
def test_image():
|
||||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
||||||
ImageQt.ImageQt(hopper(mode))
|
ImageQt.ImageQt(hopper(mode))
|
||||||
|
|
||||||
|
|
||||||
|
def test_closed_file():
|
||||||
|
with pytest.warns(None) as record:
|
||||||
|
ImageQt.ImageQt("Tests/images/hopper.gif")
|
||||||
|
|
||||||
|
assert not record
|
||||||
|
|
|
@ -62,4 +62,20 @@ def test_viewer():
|
||||||
|
|
||||||
def test_viewers():
|
def test_viewers():
|
||||||
for viewer in ImageShow._viewers:
|
for viewer in ImageShow._viewers:
|
||||||
|
try:
|
||||||
viewer.get_command("test.jpg")
|
viewer.get_command("test.jpg")
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipythonviewer():
|
||||||
|
pytest.importorskip("IPython", reason="IPython not installed")
|
||||||
|
for viewer in ImageShow._viewers:
|
||||||
|
if isinstance(viewer, ImageShow.IPythonViewer):
|
||||||
|
test_viewer = viewer
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
assert test_viewer.show(im) == 1
|
||||||
|
|
|
@ -320,6 +320,23 @@ class TestLibUnpack:
|
||||||
self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0))
|
self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0))
|
||||||
self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3))
|
self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3))
|
||||||
|
|
||||||
|
self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
|
||||||
|
self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
|
||||||
|
self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
|
||||||
|
|
||||||
|
self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
|
||||||
|
self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
|
||||||
|
self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))
|
||||||
|
|
||||||
|
if sys.byteorder == "little":
|
||||||
|
self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
|
||||||
|
self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
|
||||||
|
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))
|
||||||
|
else:
|
||||||
|
self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
|
||||||
|
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
|
||||||
|
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
|
||||||
|
|
||||||
def test_RGBA(self):
|
def test_RGBA(self):
|
||||||
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
|
@ -450,6 +467,43 @@ class TestLibUnpack:
|
||||||
self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
|
self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
|
||||||
self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
|
self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
|
||||||
|
|
||||||
|
self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0))
|
||||||
|
self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0))
|
||||||
|
self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0))
|
||||||
|
self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5))
|
||||||
|
|
||||||
|
self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0))
|
||||||
|
self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0))
|
||||||
|
self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0))
|
||||||
|
self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6))
|
||||||
|
|
||||||
|
if sys.byteorder == "little":
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)
|
||||||
|
)
|
||||||
|
|
||||||
def test_RGBa(self):
|
def test_RGBa(self):
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
"RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)
|
"RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)
|
||||||
|
|
|
@ -4,10 +4,6 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import is_win32
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer")
|
|
||||||
|
|
||||||
|
|
||||||
def test_overflow():
|
def test_overflow():
|
||||||
# There is the potential to overflow comparisons in map.c
|
# There is the potential to overflow comparisons in map.c
|
||||||
|
@ -27,6 +23,13 @@ def test_overflow():
|
||||||
Image.MAX_IMAGE_PIXELS = max_pixels
|
Image.MAX_IMAGE_PIXELS = max_pixels
|
||||||
|
|
||||||
|
|
||||||
|
def test_tobytes():
|
||||||
|
# Previously raised an access violation on Windows
|
||||||
|
with Image.open("Tests/images/l2rgb_read.bmp") as im:
|
||||||
|
with pytest.raises((ValueError, MemoryError, OSError)):
|
||||||
|
im.tobytes()
|
||||||
|
|
||||||
|
|
||||||
@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():
|
def test_ysize():
|
||||||
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
||||||
|
|
|
@ -2,18 +2,26 @@ import pytest
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import QPixmap
|
from PIL.ImageQt import QPixmap
|
||||||
|
|
||||||
if ImageQt.qt_version == "6":
|
if ImageQt.qt_version == "6":
|
||||||
|
from PyQt6.QtCore import QPoint
|
||||||
|
from PyQt6.QtGui import QImage, QPainter, QRegion
|
||||||
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
elif ImageQt.qt_version == "side6":
|
elif ImageQt.qt_version == "side6":
|
||||||
|
from PySide6.QtCore import QPoint
|
||||||
|
from PySide6.QtGui import QImage, QPainter, QRegion
|
||||||
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
elif ImageQt.qt_version == "5":
|
elif ImageQt.qt_version == "5":
|
||||||
|
from PyQt5.QtCore import QPoint
|
||||||
|
from PyQt5.QtGui import QImage, QPainter, QRegion
|
||||||
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
elif ImageQt.qt_version == "side2":
|
elif ImageQt.qt_version == "side2":
|
||||||
|
from PySide2.QtCore import QPoint
|
||||||
|
from PySide2.QtGui import QImage, QPainter, QRegion
|
||||||
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
|
|
||||||
class Example(QWidget):
|
class Example(QWidget):
|
||||||
|
@ -49,7 +57,8 @@ def test_sanity(tmp_path):
|
||||||
|
|
||||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
||||||
# to QPixmap
|
# to QPixmap
|
||||||
data = ImageQt.toqpixmap(hopper(mode))
|
im = hopper(mode)
|
||||||
|
data = ImageQt.toqpixmap(im)
|
||||||
|
|
||||||
assert isinstance(data, QPixmap)
|
assert isinstance(data, QPixmap)
|
||||||
assert not data.isNull()
|
assert not data.isNull()
|
||||||
|
@ -58,6 +67,20 @@ def test_sanity(tmp_path):
|
||||||
tempfile = str(tmp_path / f"temp_{mode}.png")
|
tempfile = str(tmp_path / f"temp_{mode}.png")
|
||||||
data.save(tempfile)
|
data.save(tempfile)
|
||||||
|
|
||||||
|
# Render the image
|
||||||
|
qimage = ImageQt.ImageQt(im)
|
||||||
|
data = QPixmap.fromImage(qimage)
|
||||||
|
qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage
|
||||||
|
qimage = QImage(128, 128, qt_format.Format_ARGB32)
|
||||||
|
painter = QPainter(qimage)
|
||||||
|
image_label = QLabel()
|
||||||
|
image_label.setPixmap(data)
|
||||||
|
image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
|
||||||
|
painter.end()
|
||||||
|
rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
|
||||||
|
qimage.save(rendered_tempfile)
|
||||||
|
assert_image_equal_tofile(im.convert("RGBA"), rendered_tempfile)
|
||||||
|
|
||||||
# from QPixmap
|
# from QPixmap
|
||||||
roundtrip(hopper(mode))
|
roundtrip(hopper(mode))
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,18 @@ Tk/Tcl 8.4
|
||||||
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
||||||
when Tk/Tcl 8.5 will be the minimum supported.
|
when Tk/Tcl 8.5 will be the minimum supported.
|
||||||
|
|
||||||
|
Categories
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 8.2.0
|
||||||
|
|
||||||
|
``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
||||||
|
along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and
|
||||||
|
``Image.CONTAINER`` attributes.
|
||||||
|
|
||||||
|
To determine if an image has multiple frames or not,
|
||||||
|
``getattr(im, "is_animated", False)`` can be used instead.
|
||||||
|
|
||||||
Image.show command parameter
|
Image.show command parameter
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -876,10 +876,10 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
**compression**
|
**compression**
|
||||||
A string containing the desired compression method for the
|
A string containing the desired compression method for the
|
||||||
file. (valid only with libtiff installed) Valid compression
|
file. (valid only with libtiff installed) Valid compression
|
||||||
methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``,
|
methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``,
|
||||||
``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``,
|
``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``,
|
||||||
``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``,
|
``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``,
|
||||||
``"tiff_sgilog24"``, ``"tiff_raw_16"``
|
``"webp"`, ``"zstd"``
|
||||||
|
|
||||||
**quality**
|
**quality**
|
||||||
The image quality for JPEG compression, on a scale from 0 (worst) to 100
|
The image quality for JPEG compression, on a scale from 0 (worst) to 100
|
||||||
|
@ -901,6 +901,9 @@ using the general tags available through tiffinfo.
|
||||||
**copyright**
|
**copyright**
|
||||||
Strings
|
Strings
|
||||||
|
|
||||||
|
**icc_profile**
|
||||||
|
The ICC Profile to include in the saved file.
|
||||||
|
|
||||||
**resolution_unit**
|
**resolution_unit**
|
||||||
An integer. 1 for no unit, 2 for inches and 3 for centimeters.
|
An integer. 1 for no unit, 2 for inches and 3 for centimeters.
|
||||||
|
|
||||||
|
|
|
@ -88,9 +88,10 @@ libraqm, fribidi, and harfbuzz to be installed separately::
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
Most major Linux distributions, including Fedora, Debian/Ubuntu and
|
Most major Linux distributions, including Fedora, Ubuntu and ArchLinux
|
||||||
ArchLinux also include Pillow in packages that previously contained
|
also include Pillow in packages that previously contained PIL e.g.
|
||||||
PIL e.g. ``python-imaging``.
|
``python-imaging``. Debian splits it into two packages, ``python3-pil``
|
||||||
|
and ``python3-pil.imagetk``.
|
||||||
|
|
||||||
FreeBSD Installation
|
FreeBSD Installation
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -366,6 +367,10 @@ In Fedora, the command is::
|
||||||
|
|
||||||
sudo dnf install python3-devel redhat-rpm-config
|
sudo dnf install python3-devel redhat-rpm-config
|
||||||
|
|
||||||
|
In Alpine, the command is::
|
||||||
|
|
||||||
|
sudo apk add python3-dev py3-setuptools
|
||||||
|
|
||||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||||
|
|
||||||
Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
|
Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
|
||||||
|
@ -385,6 +390,12 @@ Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with
|
||||||
Note that the package manager may be yum or DNF, depending on the
|
Note that the package manager may be yum or DNF, depending on the
|
||||||
exact distribution.
|
exact distribution.
|
||||||
|
|
||||||
|
Prerequisites are installed for **Alpine** with::
|
||||||
|
|
||||||
|
sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
|
||||||
|
libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \
|
||||||
|
libxcb-dev libpng-dev
|
||||||
|
|
||||||
See also the ``Dockerfile``\s in the Test Infrastructure repo
|
See also the ``Dockerfile``\s in the Test Infrastructure repo
|
||||||
(https://github.com/python-pillow/docker-images) for a known working
|
(https://github.com/python-pillow/docker-images) for a known working
|
||||||
install process for other tested distros.
|
install process for other tested distros.
|
||||||
|
@ -465,9 +476,9 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
|
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.0 |arm |
|
| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.2 |arm |
|
||||||
| +------------------------------+--------------------------------+-----------------------+
|
| +------------------------------+--------------------------------+-----------------------+
|
||||||
| | 3.6, 3.7, 3.8, 3.9 | 8.1.0 |x86-64 |
|
| | 3.6, 3.7, 3.8, 3.9 | 8.1.2 |x86-64 |
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 |
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 |
|
||||||
| +------------------------------+--------------------------------+ +
|
| +------------------------------+--------------------------------+ +
|
||||||
|
|
|
@ -486,15 +486,16 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m
|
||||||
|
|
||||||
.. data:: MEDIANCUT
|
.. data:: MEDIANCUT
|
||||||
|
|
||||||
Median cut
|
Median cut. Default method, except for RGBA images. This method does not support
|
||||||
|
RGBA images.
|
||||||
|
|
||||||
.. data:: MAXCOVERAGE
|
.. data:: MAXCOVERAGE
|
||||||
|
|
||||||
Maximum coverage
|
Maximum coverage. This method does not support RGBA images.
|
||||||
|
|
||||||
.. data:: FASTOCTREE
|
.. data:: FASTOCTREE
|
||||||
|
|
||||||
Fast octree
|
Fast octree. Default method for RGBA images.
|
||||||
|
|
||||||
.. data:: LIBIMAGEQUANT
|
.. data:: LIBIMAGEQUANT
|
||||||
|
|
||||||
|
@ -502,10 +503,3 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m
|
||||||
|
|
||||||
Check support using :py:func:`PIL.features.check_feature`
|
Check support using :py:func:`PIL.features.check_feature`
|
||||||
with ``feature="libimagequant"``.
|
with ``feature="libimagequant"``.
|
||||||
|
|
||||||
.. comment: These are not referenced anywhere?
|
|
||||||
Categories
|
|
||||||
^^^^^^^^^^
|
|
||||||
.. data:: NORMAL
|
|
||||||
.. data:: SEQUENCE
|
|
||||||
.. data:: CONTAINER
|
|
||||||
|
|
|
@ -285,6 +285,20 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
|
Draws a rounded rectangle.
|
||||||
|
|
||||||
|
:param xy: Two points to define the bounding box. Sequence of either
|
||||||
|
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point
|
||||||
|
is just outside the drawn rectangle.
|
||||||
|
:param radius: Radius of the corners.
|
||||||
|
:param outline: Color to use for the outline.
|
||||||
|
:param fill: Color to use for the fill.
|
||||||
|
:param width: The line width, in pixels.
|
||||||
|
|
||||||
|
.. versionadded:: 8.2.0
|
||||||
|
|
||||||
.. py:method:: ImageDraw.shape(shape, fill=None, outline=None)
|
.. py:method:: ImageDraw.shape(shape, fill=None, outline=None)
|
||||||
|
|
||||||
.. warning:: This method is experimental.
|
.. warning:: This method is experimental.
|
||||||
|
@ -352,7 +366,7 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
@ -413,7 +427,7 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
@ -577,7 +591,7 @@ Methods
|
||||||
correct substitutions as appropriate, if available.
|
correct substitutions as appropriate, if available.
|
||||||
It should be a `BCP 47 language code`_.
|
It should be a `BCP 47 language code`_.
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
|
||||||
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||||
|
|
||||||
|
@ -626,7 +640,7 @@ Methods
|
||||||
It should be a `BCP 47 language code`_.
|
It should be a `BCP 47 language code`_.
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param stroke_width: The width of the text stroke.
|
:param stroke_width: The width of the text stroke.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
|
||||||
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||||
|
|
||||||
|
@ -669,7 +683,7 @@ Methods
|
||||||
It should be a `BCP 47 language code`_.
|
It should be a `BCP 47 language code`_.
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param stroke_width: The width of the text stroke.
|
:param stroke_width: The width of the text stroke.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
|
||||||
.. py:method:: getdraw(im=None, hints=None)
|
.. py:method:: getdraw(im=None, hints=None)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ All default viewers convert the image to be shown to PNG format.
|
||||||
|
|
||||||
.. autofunction:: PIL.ImageShow.show
|
.. autofunction:: PIL.ImageShow.show
|
||||||
|
|
||||||
|
.. autoclass:: IPythonViewer
|
||||||
.. autoclass:: WindowsViewer
|
.. autoclass:: WindowsViewer
|
||||||
.. autoclass:: MacViewer
|
.. autoclass:: MacViewer
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,9 @@ now support fonts with embedded color data.
|
||||||
To render text with embedded color data, use the parameter ``embedded_color=True``.
|
To render text with embedded color data, use the parameter ``embedded_color=True``.
|
||||||
|
|
||||||
Support for CBDT fonts requires FreeType 2.5 compiled with libpng.
|
Support for CBDT fonts requires FreeType 2.5 compiled with libpng.
|
||||||
|
Support for SBIX fonts requires FreeType 2.5.1 compiled with libpng.
|
||||||
Support for COLR fonts requires FreeType 2.10.
|
Support for COLR fonts requires FreeType 2.10.
|
||||||
SBIX and SVG fonts are not yet supported.
|
SVG fonts are not yet supported.
|
||||||
|
|
||||||
ImageDraw.textlength
|
ImageDraw.textlength
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -10,21 +10,78 @@ Tk/Tcl 8.4
|
||||||
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
||||||
when Tk/Tcl 8.5 will be the minimum supported.
|
when Tk/Tcl 8.5 will be the minimum supported.
|
||||||
|
|
||||||
|
Categories
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
|
||||||
|
along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and
|
||||||
|
``Image.CONTAINER`` attributes.
|
||||||
|
|
||||||
|
To determine if an image has multiple frames or not,
|
||||||
|
``getattr(im, "is_animated", False)`` can be used instead.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
Image.alpha_composite: dest
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
When calling :py:meth:`~PIL.Image.Image.alpha_composite`, the ``dest`` argument now
|
||||||
|
accepts negative co-ordinates, like the upper left corner of the ``box`` argument of
|
||||||
|
:py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of
|
||||||
|
cropping the overlaid image.
|
||||||
|
|
||||||
|
Image.getexif: EXIF and GPS IFD
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Previously, :py:meth:`~PIL.Image.Image.getexif` flattened the EXIF IFD into the rest of
|
||||||
|
the data, losing information. This information is now kept separate, moved under
|
||||||
|
``im.getexif().get_ifd(0x8769)``.
|
||||||
|
|
||||||
|
Direct access to the GPS IFD dictionary was possible through ``im.getexif()[0x8825]``.
|
||||||
|
This is now consistent with other IFDs, and must be accessed through
|
||||||
|
``im.getexif().get_ifd(0x8825)``.
|
||||||
|
|
||||||
|
These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pillow
|
||||||
|
6.0. The older ``_getexif()`` methods are unaffected.
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
ImageDraw.rounded_rectangle
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as
|
||||||
|
:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius``
|
||||||
|
argument. ``radius`` is limited to half of the width or the height, so that users can
|
||||||
|
create a circle, but not any other ellipse.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
im = Image.new("RGB", (200, 200))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red")
|
||||||
|
|
||||||
|
ImageShow.IPythonViewer
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be
|
||||||
|
registered. It displays images on all IPython frontends. This will be helpful
|
||||||
|
to users of Google Colab, allowing ``im.show()`` to display images.
|
||||||
|
|
||||||
|
It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer`
|
||||||
|
instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()`
|
||||||
|
if none of the other viewers are available. This means that the behaviour of
|
||||||
|
:py:class:`PIL.ImageShow` will stay the same for most Pillow users.
|
||||||
|
|
||||||
|
Saving TIFF with ICC profile
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
As is already possible for JPEG, PNG and WebP, the ICC profile for TIFF files can now
|
||||||
|
be specified through a keyword argument::
|
||||||
|
|
||||||
|
im.save("out.tif", icc_profile=...)
|
||||||
|
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
|
@ -8,6 +8,7 @@ packaging
|
||||||
pyroma
|
pyroma
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
pytest-timeout
|
||||||
sphinx>=2.4
|
sphinx>=2.4
|
||||||
sphinx-issues
|
sphinx-issues
|
||||||
sphinx-removed-in
|
sphinx-removed-in
|
||||||
|
|
120
setup.py
|
@ -29,6 +29,8 @@ def get_version():
|
||||||
NAME = "Pillow"
|
NAME = "Pillow"
|
||||||
PILLOW_VERSION = get_version()
|
PILLOW_VERSION = get_version()
|
||||||
FREETYPE_ROOT = None
|
FREETYPE_ROOT = None
|
||||||
|
HARFBUZZ_ROOT = None
|
||||||
|
FRIBIDI_ROOT = None
|
||||||
IMAGEQUANT_ROOT = None
|
IMAGEQUANT_ROOT = None
|
||||||
JPEG2K_ROOT = None
|
JPEG2K_ROOT = None
|
||||||
JPEG_ROOT = None
|
JPEG_ROOT = None
|
||||||
|
@ -228,6 +230,19 @@ def _find_library_file(self, library):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def _find_include_dir(self, dirname, include):
|
||||||
|
for directory in self.compiler.include_dirs:
|
||||||
|
_dbg("Checking for include file %s in %s", (include, directory))
|
||||||
|
if os.path.isfile(os.path.join(directory, include)):
|
||||||
|
_dbg("Found %s in %s", (include, directory))
|
||||||
|
return True
|
||||||
|
subdir = os.path.join(directory, dirname)
|
||||||
|
_dbg("Checking for include file %s in %s", (include, subdir))
|
||||||
|
if os.path.isfile(os.path.join(subdir, include)):
|
||||||
|
_dbg("Found %s in %s", (include, subdir))
|
||||||
|
return subdir
|
||||||
|
|
||||||
|
|
||||||
def _cmd_exists(cmd):
|
def _cmd_exists(cmd):
|
||||||
return any(
|
return any(
|
||||||
os.access(os.path.join(path, cmd), os.X_OK)
|
os.access(os.path.join(path, cmd), os.X_OK)
|
||||||
|
@ -267,6 +282,7 @@ class pil_build_ext(build_ext):
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"tiff",
|
"tiff",
|
||||||
"freetype",
|
"freetype",
|
||||||
|
"raqm",
|
||||||
"lcms",
|
"lcms",
|
||||||
"webp",
|
"webp",
|
||||||
"webpmux",
|
"webpmux",
|
||||||
|
@ -276,6 +292,7 @@ class pil_build_ext(build_ext):
|
||||||
]
|
]
|
||||||
|
|
||||||
required = {"jpeg", "zlib"}
|
required = {"jpeg", "zlib"}
|
||||||
|
vendor = set()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
for f in self.features:
|
for f in self.features:
|
||||||
|
@ -287,6 +304,9 @@ class pil_build_ext(build_ext):
|
||||||
def want(self, feat):
|
def want(self, feat):
|
||||||
return getattr(self, feat) is None
|
return getattr(self, feat) is None
|
||||||
|
|
||||||
|
def want_vendor(self, feat):
|
||||||
|
return feat in self.vendor
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
yield from self.features
|
yield from self.features
|
||||||
|
|
||||||
|
@ -296,6 +316,10 @@ class pil_build_ext(build_ext):
|
||||||
build_ext.user_options
|
build_ext.user_options
|
||||||
+ [(f"disable-{x}", None, f"Disable support for {x}") for x in feature]
|
+ [(f"disable-{x}", None, f"Disable support for {x}") for x in feature]
|
||||||
+ [(f"enable-{x}", None, f"Enable support for {x}") for x in feature]
|
+ [(f"enable-{x}", None, f"Enable support for {x}") for x in feature]
|
||||||
|
+ [
|
||||||
|
(f"vendor-{x}", None, f"Use vendored version of {x}")
|
||||||
|
for x in ("raqm", "fribidi")
|
||||||
|
]
|
||||||
+ [
|
+ [
|
||||||
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
|
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
|
||||||
("debug", None, "Debug logging"),
|
("debug", None, "Debug logging"),
|
||||||
|
@ -310,6 +334,8 @@ class pil_build_ext(build_ext):
|
||||||
for x in self.feature:
|
for x in self.feature:
|
||||||
setattr(self, f"disable_{x}", None)
|
setattr(self, f"disable_{x}", None)
|
||||||
setattr(self, f"enable_{x}", None)
|
setattr(self, f"enable_{x}", None)
|
||||||
|
for x in ("raqm", "fribidi"):
|
||||||
|
setattr(self, f"vendor_{x}", None)
|
||||||
|
|
||||||
def finalize_options(self):
|
def finalize_options(self):
|
||||||
build_ext.finalize_options(self)
|
build_ext.finalize_options(self)
|
||||||
|
@ -334,18 +360,40 @@ class pil_build_ext(build_ext):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Conflicting options: --enable-{x} and --disable-{x}"
|
f"Conflicting options: --enable-{x} and --disable-{x}"
|
||||||
)
|
)
|
||||||
|
if x == "freetype":
|
||||||
|
_dbg("--disable-freetype implies --disable-raqm")
|
||||||
|
if getattr(self, "enable_raqm"):
|
||||||
|
raise ValueError(
|
||||||
|
"Conflicting options: --enable-raqm and --disable-freetype"
|
||||||
|
)
|
||||||
|
setattr(self, "disable_raqm", True)
|
||||||
if getattr(self, f"enable_{x}"):
|
if getattr(self, f"enable_{x}"):
|
||||||
_dbg("Requiring %s", x)
|
_dbg("Requiring %s", x)
|
||||||
self.feature.required.add(x)
|
self.feature.required.add(x)
|
||||||
|
if x == "raqm":
|
||||||
|
_dbg("--enable-raqm implies --enable-freetype")
|
||||||
|
self.feature.required.add("freetype")
|
||||||
|
for x in ("raqm", "fribidi"):
|
||||||
|
if getattr(self, f"vendor_{x}"):
|
||||||
|
if getattr(self, "disable_raqm"):
|
||||||
|
raise ValueError(
|
||||||
|
f"Conflicting options: --vendor-{x} and --disable-raqm"
|
||||||
|
)
|
||||||
|
if x == "fribidi" and not getattr(self, "vendor_raqm"):
|
||||||
|
raise ValueError(
|
||||||
|
f"Conflicting options: --vendor-{x} and not --vendor-raqm"
|
||||||
|
)
|
||||||
|
_dbg("Using vendored version of %s", x)
|
||||||
|
self.feature.vendor.add(x)
|
||||||
|
|
||||||
def _update_extension(self, name, libraries, define_macros=None, include_dirs=None):
|
def _update_extension(self, name, libraries, define_macros=None, sources=None):
|
||||||
for extension in self.extensions:
|
for extension in self.extensions:
|
||||||
if extension.name == name:
|
if extension.name == name:
|
||||||
extension.libraries += libraries
|
extension.libraries += libraries
|
||||||
if define_macros is not None:
|
if define_macros is not None:
|
||||||
extension.define_macros += define_macros
|
extension.define_macros += define_macros
|
||||||
if include_dirs is not None:
|
if sources is not None:
|
||||||
extension.include_dirs += include_dirs
|
extension.sources += sources
|
||||||
if FUZZING_BUILD:
|
if FUZZING_BUILD:
|
||||||
extension.language = "c++"
|
extension.language = "c++"
|
||||||
extension.extra_link_args = ["--stdlib=libc++"]
|
extension.extra_link_args = ["--stdlib=libc++"]
|
||||||
|
@ -374,6 +422,8 @@ class pil_build_ext(build_ext):
|
||||||
TIFF_ROOT=("libtiff-5", "libtiff-4"),
|
TIFF_ROOT=("libtiff-5", "libtiff-4"),
|
||||||
ZLIB_ROOT="zlib",
|
ZLIB_ROOT="zlib",
|
||||||
FREETYPE_ROOT="freetype2",
|
FREETYPE_ROOT="freetype2",
|
||||||
|
HARFBUZZ_ROOT="harfbuzz",
|
||||||
|
FRIBIDI_ROOT="fribidi",
|
||||||
LCMS_ROOT="lcms2",
|
LCMS_ROOT="lcms2",
|
||||||
IMAGEQUANT_ROOT="libimagequant",
|
IMAGEQUANT_ROOT="libimagequant",
|
||||||
).items():
|
).items():
|
||||||
|
@ -659,6 +709,39 @@ class pil_build_ext(build_ext):
|
||||||
if subdir:
|
if subdir:
|
||||||
_add_directory(self.compiler.include_dirs, subdir, 0)
|
_add_directory(self.compiler.include_dirs, subdir, 0)
|
||||||
|
|
||||||
|
if feature.freetype and feature.want("raqm"):
|
||||||
|
if not feature.want_vendor("raqm"): # want system Raqm
|
||||||
|
_dbg("Looking for Raqm")
|
||||||
|
if _find_include_file(self, "raqm.h"):
|
||||||
|
if _find_library_file(self, "raqm"):
|
||||||
|
feature.raqm = "raqm"
|
||||||
|
elif _find_library_file(self, "libraqm"):
|
||||||
|
feature.raqm = "libraqm"
|
||||||
|
else: # want to build Raqm from src/thirdparty
|
||||||
|
_dbg("Looking for HarfBuzz")
|
||||||
|
feature.harfbuzz = None
|
||||||
|
hb_dir = _find_include_dir(self, "harfbuzz", "hb.h")
|
||||||
|
if hb_dir:
|
||||||
|
if isinstance(hb_dir, str):
|
||||||
|
_add_directory(self.compiler.include_dirs, hb_dir, 0)
|
||||||
|
if _find_library_file(self, "harfbuzz"):
|
||||||
|
feature.harfbuzz = "harfbuzz"
|
||||||
|
if feature.harfbuzz:
|
||||||
|
if not feature.want_vendor("fribidi"): # want system FriBiDi
|
||||||
|
_dbg("Looking for FriBiDi")
|
||||||
|
feature.fribidi = None
|
||||||
|
fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h")
|
||||||
|
if fribidi_dir:
|
||||||
|
if isinstance(fribidi_dir, str):
|
||||||
|
_add_directory(
|
||||||
|
self.compiler.include_dirs, fribidi_dir, 0
|
||||||
|
)
|
||||||
|
if _find_library_file(self, "fribidi"):
|
||||||
|
feature.fribidi = "fribidi"
|
||||||
|
feature.raqm = True
|
||||||
|
else: # want to build FriBiDi shim from src/thirdparty
|
||||||
|
feature.raqm = True
|
||||||
|
|
||||||
if feature.want("lcms"):
|
if feature.want("lcms"):
|
||||||
_dbg("Looking for lcms")
|
_dbg("Looking for lcms")
|
||||||
if _find_include_file(self, "lcms2.h"):
|
if _find_include_file(self, "lcms2.h"):
|
||||||
|
@ -754,9 +837,25 @@ class pil_build_ext(build_ext):
|
||||||
# additional libraries
|
# additional libraries
|
||||||
|
|
||||||
if feature.freetype:
|
if feature.freetype:
|
||||||
|
srcs = []
|
||||||
libs = ["freetype"]
|
libs = ["freetype"]
|
||||||
defs = []
|
defs = []
|
||||||
self._update_extension("PIL._imagingft", libs, defs)
|
if feature.raqm:
|
||||||
|
if not feature.want_vendor("raqm"): # using system Raqm
|
||||||
|
defs.append(("HAVE_RAQM", None))
|
||||||
|
defs.append(("HAVE_RAQM_SYSTEM", None))
|
||||||
|
libs.append(feature.raqm)
|
||||||
|
else: # building Raqm from src/thirdparty
|
||||||
|
defs.append(("HAVE_RAQM", None))
|
||||||
|
srcs.append("src/thirdparty/raqm/raqm.c")
|
||||||
|
libs.append(feature.harfbuzz)
|
||||||
|
if not feature.want_vendor("fribidi"): # using system FriBiDi
|
||||||
|
defs.append(("HAVE_FRIBIDI_SYSTEM", None))
|
||||||
|
libs.append(feature.fribidi)
|
||||||
|
else: # building FriBiDi shim from src/thirdparty
|
||||||
|
srcs.append("src/thirdparty/fribidi-shim/fribidi.c")
|
||||||
|
self._update_extension("PIL._imagingft", libs, defs, srcs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._remove_extension("PIL._imagingft")
|
self._remove_extension("PIL._imagingft")
|
||||||
|
|
||||||
|
@ -803,6 +902,12 @@ class pil_build_ext(build_ext):
|
||||||
print(f" [{v.strip()}")
|
print(f" [{v.strip()}")
|
||||||
print("-" * 68)
|
print("-" * 68)
|
||||||
|
|
||||||
|
raqm_extra_info = ""
|
||||||
|
if feature.want_vendor("raqm"):
|
||||||
|
raqm_extra_info += "bundled"
|
||||||
|
if feature.want_vendor("fribidi"):
|
||||||
|
raqm_extra_info += ", FriBiDi shim"
|
||||||
|
|
||||||
options = [
|
options = [
|
||||||
(feature.jpeg, "JPEG"),
|
(feature.jpeg, "JPEG"),
|
||||||
(feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version),
|
(feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version),
|
||||||
|
@ -810,6 +915,7 @@ class pil_build_ext(build_ext):
|
||||||
(feature.imagequant, "LIBIMAGEQUANT"),
|
(feature.imagequant, "LIBIMAGEQUANT"),
|
||||||
(feature.tiff, "LIBTIFF"),
|
(feature.tiff, "LIBTIFF"),
|
||||||
(feature.freetype, "FREETYPE2"),
|
(feature.freetype, "FREETYPE2"),
|
||||||
|
(feature.raqm, "RAQM (Text shaping)", raqm_extra_info),
|
||||||
(feature.lcms, "LITTLECMS2"),
|
(feature.lcms, "LITTLECMS2"),
|
||||||
(feature.webp, "WEBP"),
|
(feature.webp, "WEBP"),
|
||||||
(feature.webpmux, "WEBPMUX"),
|
(feature.webpmux, "WEBPMUX"),
|
||||||
|
@ -819,10 +925,10 @@ class pil_build_ext(build_ext):
|
||||||
all = 1
|
all = 1
|
||||||
for option in options:
|
for option in options:
|
||||||
if option[0]:
|
if option[0]:
|
||||||
version = ""
|
extra_info = ""
|
||||||
if len(option) >= 3 and option[2]:
|
if len(option) >= 3 and option[2]:
|
||||||
version = f" ({option[2]})"
|
extra_info = f" ({option[2]})"
|
||||||
print(f"--- {option[1]} support available{version}")
|
print(f"--- {option[1]} support available{extra_info}")
|
||||||
else:
|
else:
|
||||||
print(f"*** {option[1]} support not available")
|
print(f"*** {option[1]} support not available")
|
||||||
all = 0
|
all = 0
|
||||||
|
|
|
@ -265,16 +265,20 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
elif self.disposal_method == 2:
|
elif self.disposal_method == 2:
|
||||||
# replace with background colour
|
# replace with background colour
|
||||||
Image._decompression_bomb_check(self.size)
|
|
||||||
self.dispose = Image.core.fill("P", self.size, self.info["background"])
|
# only dispose the extent in this frame
|
||||||
|
x0, y0, x1, y1 = self.dispose_extent
|
||||||
|
dispose_size = (x1 - x0, y1 - y0)
|
||||||
|
|
||||||
|
Image._decompression_bomb_check(dispose_size)
|
||||||
|
self.dispose = Image.core.fill(
|
||||||
|
"P", dispose_size, self.info["background"]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# replace with previous contents
|
# replace with previous contents
|
||||||
if self.im:
|
if self.im:
|
||||||
self.dispose = self.im.copy()
|
|
||||||
|
|
||||||
# only dispose the extent in this frame
|
# only dispose the extent in this frame
|
||||||
if self.dispose:
|
self.dispose = self._crop(self.im, self.dispose_extent)
|
||||||
self.dispose = self._crop(self.dispose, self.dispose_extent)
|
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
127
src/PIL/Image.py
|
@ -59,6 +59,16 @@ if sys.version_info >= (3, 7):
|
||||||
if name == "PILLOW_VERSION":
|
if name == "PILLOW_VERSION":
|
||||||
_raise_version_warning()
|
_raise_version_warning()
|
||||||
return __version__
|
return __version__
|
||||||
|
else:
|
||||||
|
categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
|
||||||
|
if name in categories:
|
||||||
|
warnings.warn(
|
||||||
|
"Image categories are deprecated and will be removed in Pillow 10 "
|
||||||
|
"(2023-01-02). Use is_animated instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return categories[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,6 +79,11 @@ else:
|
||||||
# Silence warning
|
# Silence warning
|
||||||
assert PILLOW_VERSION
|
assert PILLOW_VERSION
|
||||||
|
|
||||||
|
# categories
|
||||||
|
NORMAL = 0
|
||||||
|
SEQUENCE = 1
|
||||||
|
CONTAINER = 2
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -187,11 +202,6 @@ MAXCOVERAGE = 1
|
||||||
FASTOCTREE = 2
|
FASTOCTREE = 2
|
||||||
LIBIMAGEQUANT = 3
|
LIBIMAGEQUANT = 3
|
||||||
|
|
||||||
# categories
|
|
||||||
NORMAL = 0
|
|
||||||
SEQUENCE = 1
|
|
||||||
CONTAINER = 2
|
|
||||||
|
|
||||||
if hasattr(core, "DEFAULT_STRATEGY"):
|
if hasattr(core, "DEFAULT_STRATEGY"):
|
||||||
DEFAULT_STRATEGY = core.DEFAULT_STRATEGY
|
DEFAULT_STRATEGY = core.DEFAULT_STRATEGY
|
||||||
FILTERED = core.FILTERED
|
FILTERED = core.FILTERED
|
||||||
|
@ -535,11 +545,22 @@ class Image:
|
||||||
self._size = (0, 0)
|
self._size = (0, 0)
|
||||||
self.palette = None
|
self.palette = None
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.category = NORMAL
|
self._category = 0
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
self._exif = None
|
self._exif = None
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name == "category":
|
||||||
|
warnings.warn(
|
||||||
|
"Image categories are deprecated and will be removed in Pillow 10 "
|
||||||
|
"(2023-01-02). Use is_animated instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self._category
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
return self.size[0]
|
return self.size[0]
|
||||||
|
@ -648,7 +669,7 @@ class Image:
|
||||||
and self.mode == other.mode
|
and self.mode == other.mode
|
||||||
and self.size == other.size
|
and self.size == other.size
|
||||||
and self.info == other.info
|
and self.info == other.info
|
||||||
and self.category == other.category
|
and self._category == other._category
|
||||||
and self.readonly == other.readonly
|
and self.readonly == other.readonly
|
||||||
and self.getpalette() == other.getpalette()
|
and self.getpalette() == other.getpalette()
|
||||||
and self.tobytes() == other.tobytes()
|
and self.tobytes() == other.tobytes()
|
||||||
|
@ -1059,6 +1080,12 @@ class Image:
|
||||||
:data:`LIBIMAGEQUANT` (libimagequant; check support using
|
:data:`LIBIMAGEQUANT` (libimagequant; check support using
|
||||||
:py:func:`PIL.features.check_feature`
|
:py:func:`PIL.features.check_feature`
|
||||||
with ``feature="libimagequant"``).
|
with ``feature="libimagequant"``).
|
||||||
|
|
||||||
|
By default, :data:`MEDIANCUT` will be used.
|
||||||
|
|
||||||
|
The exception to this is RGBA images. :data:`MEDIANCUT` and
|
||||||
|
:data:`MAXCOVERAGE` do not support RGBA images, so
|
||||||
|
:data:`FASTOCTREE` is used by default instead.
|
||||||
:param kmeans: Integer
|
:param kmeans: Integer
|
||||||
:param palette: Quantize to the palette of given
|
:param palette: Quantize to the palette of given
|
||||||
:py:class:`PIL.Image.Image`.
|
:py:class:`PIL.Image.Image`.
|
||||||
|
@ -1074,11 +1101,11 @@ class Image:
|
||||||
|
|
||||||
if method is None:
|
if method is None:
|
||||||
# defaults:
|
# defaults:
|
||||||
method = 0
|
method = MEDIANCUT
|
||||||
if self.mode == "RGBA":
|
if self.mode == "RGBA":
|
||||||
method = 2
|
method = FASTOCTREE
|
||||||
|
|
||||||
if self.mode == "RGBA" and method not in (2, 3):
|
if self.mode == "RGBA" and method not in (FASTOCTREE, LIBIMAGEQUANT):
|
||||||
# Caller specified an invalid mode.
|
# Caller specified an invalid mode.
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Fast Octree (method == 2) and libimagequant (method == 3) "
|
"Fast Octree (method == 2) and libimagequant (method == 3) "
|
||||||
|
@ -1544,8 +1571,6 @@ class Image:
|
||||||
raise ValueError("Destination must be a 2-tuple")
|
raise ValueError("Destination must be a 2-tuple")
|
||||||
if min(source) < 0:
|
if min(source) < 0:
|
||||||
raise ValueError("Source must be non-negative")
|
raise ValueError("Source must be non-negative")
|
||||||
if min(dest) < 0:
|
|
||||||
raise ValueError("Destination must be non-negative")
|
|
||||||
|
|
||||||
if len(source) == 2:
|
if len(source) == 2:
|
||||||
source = source + im.size
|
source = source + im.size
|
||||||
|
@ -1912,7 +1937,7 @@ class Image:
|
||||||
if self.mode in ("1", "P"):
|
if self.mode in ("1", "P"):
|
||||||
resample = NEAREST
|
resample = NEAREST
|
||||||
|
|
||||||
if self.mode in ["LA", "RGBA"]:
|
if self.mode in ["LA", "RGBA"] and resample != NEAREST:
|
||||||
im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
||||||
im = im.resize(size, resample, box)
|
im = im.resize(size, resample, box)
|
||||||
return im.convert(self.mode)
|
return im.convert(self.mode)
|
||||||
|
@ -2396,7 +2421,7 @@ class Image:
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.mode in ("LA", "RGBA"):
|
if self.mode in ("LA", "RGBA") and resample != NEAREST:
|
||||||
return (
|
return (
|
||||||
self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
||||||
.transform(size, method, data, resample, fill, fillcolor)
|
.transform(size, method, data, resample, fill, fillcolor)
|
||||||
|
@ -3302,11 +3327,11 @@ class Exif(MutableMapping):
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
return {k: self._fixup(v) for k, v in src_dict.items()}
|
return {k: self._fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
def _get_ifd_dict(self, tag):
|
def _get_ifd_dict(self, offset):
|
||||||
try:
|
try:
|
||||||
# an offset pointer to the location of the nested embedded IFD.
|
# an offset pointer to the location of the nested embedded IFD.
|
||||||
# It should be a long, but may be corrupted.
|
# It should be a long, but may be corrupted.
|
||||||
self.fp.seek(self[tag])
|
self.fp.seek(offset)
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -3344,11 +3369,20 @@ class Exif(MutableMapping):
|
||||||
self.fp.seek(self._info.next)
|
self.fp.seek(self._info.next)
|
||||||
self._info.load(self.fp)
|
self._info.load(self.fp)
|
||||||
|
|
||||||
|
def _get_merged_dict(self):
|
||||||
|
merged_dict = dict(self)
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
ifd = self._get_ifd_dict(0x8769)
|
if 0x8769 in self:
|
||||||
|
ifd = self._get_ifd_dict(self[0x8769])
|
||||||
if ifd:
|
if ifd:
|
||||||
self._data.update(ifd)
|
merged_dict.update(ifd)
|
||||||
self._ifds[0x8769] = ifd
|
|
||||||
|
# GPS
|
||||||
|
if 0x8825 in self:
|
||||||
|
merged_dict[0x8825] = self._get_ifd_dict(self[0x8825])
|
||||||
|
|
||||||
|
return merged_dict
|
||||||
|
|
||||||
def tobytes(self, offset=8):
|
def tobytes(self, offset=8):
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
@ -3359,21 +3393,36 @@ class Exif(MutableMapping):
|
||||||
head = b"MM\x00\x2A\x00\x00\x00\x08"
|
head = b"MM\x00\x2A\x00\x00\x00\x08"
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
||||||
for tag, value in self.items():
|
for tag, value in self.items():
|
||||||
|
if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict):
|
||||||
|
value = self.get_ifd(tag)
|
||||||
|
if (
|
||||||
|
tag == 0x8769
|
||||||
|
and 0xA005 in value
|
||||||
|
and not isinstance(value[0xA005], dict)
|
||||||
|
):
|
||||||
|
value = value.copy()
|
||||||
|
value[0xA005] = self.get_ifd(0xA005)
|
||||||
ifd[tag] = value
|
ifd[tag] = value
|
||||||
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
||||||
|
|
||||||
def get_ifd(self, tag):
|
def get_ifd(self, tag):
|
||||||
if tag not in self._ifds and tag in self:
|
if tag not in self._ifds:
|
||||||
if tag in [0x8825, 0xA005]:
|
if tag in [0x8769, 0x8825]:
|
||||||
# gpsinfo, interop
|
# exif, gpsinfo
|
||||||
self._ifds[tag] = self._get_ifd_dict(tag)
|
if tag in self:
|
||||||
elif tag == 0x927C: # makernote
|
self._ifds[tag] = self._get_ifd_dict(self[tag])
|
||||||
|
elif tag in [0xA005, 0x927C]:
|
||||||
|
# interop, makernote
|
||||||
|
if 0x8769 not in self._ifds:
|
||||||
|
self.get_ifd(0x8769)
|
||||||
|
tag_data = self._ifds[0x8769][tag]
|
||||||
|
if tag == 0x927C:
|
||||||
|
# makernote
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if self[0x927C][:8] == b"FUJIFILM":
|
if tag_data[:8] == b"FUJIFILM":
|
||||||
exif_data = self[0x927C]
|
ifd_offset = i32le(tag_data, 8)
|
||||||
ifd_offset = i32le(exif_data, 8)
|
ifd_data = tag_data[ifd_offset:]
|
||||||
ifd_data = exif_data[ifd_offset:]
|
|
||||||
|
|
||||||
makernote = {}
|
makernote = {}
|
||||||
for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
|
for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
|
||||||
|
@ -3381,9 +3430,10 @@ class Exif(MutableMapping):
|
||||||
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
unit_size, handler = ImageFileDirectory_v2._load_dispatch[
|
(
|
||||||
typ
|
unit_size,
|
||||||
]
|
handler,
|
||||||
|
) = ImageFileDirectory_v2._load_dispatch[typ]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
size = count * unit_size
|
size = count * unit_size
|
||||||
|
@ -3407,14 +3457,12 @@ class Exif(MutableMapping):
|
||||||
makernote[ifd_tag] = handler(
|
makernote[ifd_tag] = handler(
|
||||||
ImageFileDirectory_v2(), data, False
|
ImageFileDirectory_v2(), data, False
|
||||||
)
|
)
|
||||||
self._ifds[0x927C] = dict(self._fixup_dict(makernote))
|
self._ifds[tag] = dict(self._fixup_dict(makernote))
|
||||||
elif self.get(0x010F) == "Nintendo":
|
elif self.get(0x010F) == "Nintendo":
|
||||||
ifd_data = self[0x927C]
|
|
||||||
|
|
||||||
makernote = {}
|
makernote = {}
|
||||||
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
|
for i in range(0, struct.unpack(">H", tag_data[:2])[0]):
|
||||||
ifd_tag, typ, count, data = struct.unpack(
|
ifd_tag, typ, count, data = struct.unpack(
|
||||||
">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||||
)
|
)
|
||||||
if ifd_tag == 0x1101:
|
if ifd_tag == 0x1101:
|
||||||
# CameraInfo
|
# CameraInfo
|
||||||
|
@ -3443,7 +3491,10 @@ class Exif(MutableMapping):
|
||||||
camerainfo["Category"] = self.fp.read(2)
|
camerainfo["Category"] = self.fp.read(2)
|
||||||
|
|
||||||
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||||
self._ifds[0x927C] = makernote
|
self._ifds[tag] = makernote
|
||||||
|
else:
|
||||||
|
# interop
|
||||||
|
self._ifds[tag] = self._get_ifd_dict(tag_data)
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -3463,8 +3514,6 @@ class Exif(MutableMapping):
|
||||||
def __getitem__(self, tag):
|
def __getitem__(self, tag):
|
||||||
if self._info is not None and tag not in self._data and tag in self._info:
|
if self._info is not None and tag not in self._data and tag in self._info:
|
||||||
self._data[tag] = self._fixup(self._info[tag])
|
self._data[tag] = self._fixup(self._info[tag])
|
||||||
if tag == 0x8825:
|
|
||||||
self._data[tag] = self.get_ifd(tag)
|
|
||||||
del self._info[tag]
|
del self._info[tag]
|
||||||
return self._data[tag]
|
return self._data[tag]
|
||||||
|
|
||||||
|
|
|
@ -257,6 +257,96 @@ class ImageDraw:
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_rectangle(xy, ink, 0, width)
|
self.draw.draw_rectangle(xy, ink, 0, width)
|
||||||
|
|
||||||
|
def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1):
|
||||||
|
"""Draw a rounded rectangle."""
|
||||||
|
if isinstance(xy[0], (list, tuple)):
|
||||||
|
(x0, y0), (x1, y1) = xy
|
||||||
|
else:
|
||||||
|
x0, y0, x1, y1 = xy
|
||||||
|
|
||||||
|
d = radius * 2
|
||||||
|
|
||||||
|
full_x = d >= x1 - x0
|
||||||
|
if full_x:
|
||||||
|
# The two left and two right corners are joined
|
||||||
|
d = x1 - x0
|
||||||
|
full_y = d >= y1 - y0
|
||||||
|
if full_y:
|
||||||
|
# The two top and two bottom corners are joined
|
||||||
|
d = y1 - y0
|
||||||
|
if full_x and full_y:
|
||||||
|
# If all corners are joined, that is a circle
|
||||||
|
return self.ellipse(xy, fill, outline, width)
|
||||||
|
|
||||||
|
if d == 0:
|
||||||
|
# If the corners have no curve, that is a rectangle
|
||||||
|
return self.rectangle(xy, fill, outline, width)
|
||||||
|
|
||||||
|
ink, fill = self._getink(outline, fill)
|
||||||
|
|
||||||
|
def draw_corners(pieslice):
|
||||||
|
if full_x:
|
||||||
|
# Draw top and bottom halves
|
||||||
|
parts = (
|
||||||
|
((x0, y0, x0 + d, y0 + d), 180, 360),
|
||||||
|
((x0, y1 - d, x0 + d, y1), 0, 180),
|
||||||
|
)
|
||||||
|
elif full_y:
|
||||||
|
# Draw left and right halves
|
||||||
|
parts = (
|
||||||
|
((x0, y0, x0 + d, y0 + d), 90, 270),
|
||||||
|
((x1 - d, y0, x1, y0 + d), 270, 90),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Draw four separate corners
|
||||||
|
parts = (
|
||||||
|
((x1 - d, y0, x1, y0 + d), 270, 360),
|
||||||
|
((x1 - d, y1 - d, x1, y1), 0, 90),
|
||||||
|
((x0, y1 - d, x0 + d, y1), 90, 180),
|
||||||
|
((x0, y0, x0 + d, y0 + d), 180, 270),
|
||||||
|
)
|
||||||
|
for part in parts:
|
||||||
|
if pieslice:
|
||||||
|
self.draw.draw_pieslice(*(part + (fill, 1)))
|
||||||
|
else:
|
||||||
|
self.draw.draw_arc(*(part + (ink, width)))
|
||||||
|
|
||||||
|
if fill is not None:
|
||||||
|
draw_corners(True)
|
||||||
|
|
||||||
|
if full_x:
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1
|
||||||
|
)
|
||||||
|
if not full_x and not full_y:
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1
|
||||||
|
)
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1
|
||||||
|
)
|
||||||
|
if ink is not None and ink != fill and width != 0:
|
||||||
|
draw_corners(False)
|
||||||
|
|
||||||
|
if not full_x:
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1
|
||||||
|
)
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1
|
||||||
|
)
|
||||||
|
if not full_y:
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1
|
||||||
|
)
|
||||||
|
self.draw.draw_rectangle(
|
||||||
|
(x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1
|
||||||
|
)
|
||||||
|
|
||||||
def _multiline_check(self, text):
|
def _multiline_check(self, text):
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||||
|
|
|
@ -192,21 +192,11 @@ class ImageFile(Image.Image):
|
||||||
and args[0] in Image._MAPMODES
|
and args[0] in Image._MAPMODES
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if hasattr(Image.core, "map"):
|
|
||||||
# use built-in mapper WIN32 only
|
|
||||||
self.map = Image.core.map(self.filename)
|
|
||||||
self.map.seek(offset)
|
|
||||||
self.im = self.map.readimage(
|
|
||||||
self.mode, self.size, args[1], args[2]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# use mmap, if possible
|
# use mmap, if possible
|
||||||
import mmap
|
import mmap
|
||||||
|
|
||||||
with open(self.filename) as fp:
|
with open(self.filename) as fp:
|
||||||
self.map = mmap.mmap(
|
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
||||||
fp.fileno(), 0, access=mmap.ACCESS_READ
|
|
||||||
)
|
|
||||||
self.im = Image.core.map_buffer(
|
self.im = Image.core.map_buffer(
|
||||||
self.map, self.size, decoder_name, offset, args
|
self.map, self.size, decoder_name, offset, args
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
#
|
#
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
try:
|
|
||||||
import numpy
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
numpy = None
|
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
class Filter:
|
||||||
pass
|
pass
|
||||||
|
@ -369,6 +364,13 @@ class Color3DLUT(MultibandFilter):
|
||||||
items = size[0] * size[1] * size[2]
|
items = size[0] * size[1] * size[2]
|
||||||
wrong_size = False
|
wrong_size = False
|
||||||
|
|
||||||
|
numpy = None
|
||||||
|
if hasattr(table, "shape"):
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
if numpy and isinstance(table, numpy.ndarray):
|
if numpy and isinstance(table, numpy.ndarray):
|
||||||
if copy_table:
|
if copy_table:
|
||||||
table = table.copy()
|
table = table.copy()
|
||||||
|
|
|
@ -128,6 +128,7 @@ def align8to32(bytes, width, mode):
|
||||||
def _toqclass_helper(im):
|
def _toqclass_helper(im):
|
||||||
data = None
|
data = None
|
||||||
colortable = None
|
colortable = None
|
||||||
|
exclusive_fp = False
|
||||||
|
|
||||||
# handle filename, if given instead of image name
|
# handle filename, if given instead of image name
|
||||||
if hasattr(im, "toUtf8"):
|
if hasattr(im, "toUtf8"):
|
||||||
|
@ -135,6 +136,7 @@ def _toqclass_helper(im):
|
||||||
im = str(im.toUtf8(), "utf-8")
|
im = str(im.toUtf8(), "utf-8")
|
||||||
if isPath(im):
|
if isPath(im):
|
||||||
im = Image.open(im)
|
im = Image.open(im)
|
||||||
|
exclusive_fp = True
|
||||||
|
|
||||||
qt_format = QImage.Format if qt_version == "6" else QImage
|
qt_format = QImage.Format if qt_version == "6" else QImage
|
||||||
if im.mode == "1":
|
if im.mode == "1":
|
||||||
|
@ -151,16 +153,24 @@ def _toqclass_helper(im):
|
||||||
for i in range(0, len(palette), 3):
|
for i in range(0, len(palette), 3):
|
||||||
colortable.append(rgb(*palette[i : i + 3]))
|
colortable.append(rgb(*palette[i : i + 3]))
|
||||||
elif im.mode == "RGB":
|
elif im.mode == "RGB":
|
||||||
data = im.tobytes("raw", "BGRX")
|
# Populate the 4th channel with 255
|
||||||
|
im = im.convert("RGBA")
|
||||||
|
|
||||||
|
data = im.tobytes("raw", "BGRA")
|
||||||
format = qt_format.Format_RGB32
|
format = qt_format.Format_RGB32
|
||||||
elif im.mode == "RGBA":
|
elif im.mode == "RGBA":
|
||||||
data = im.tobytes("raw", "BGRA")
|
data = im.tobytes("raw", "BGRA")
|
||||||
format = qt_format.Format_ARGB32
|
format = qt_format.Format_ARGB32
|
||||||
else:
|
else:
|
||||||
|
if exclusive_fp:
|
||||||
|
im.close()
|
||||||
raise ValueError(f"unsupported image mode {repr(im.mode)}")
|
raise ValueError(f"unsupported image mode {repr(im.mode)}")
|
||||||
|
|
||||||
__data = data or align8to32(im.tobytes(), im.size[0], im.mode)
|
size = im.size
|
||||||
return {"data": __data, "im": im, "format": format, "colortable": colortable}
|
__data = data or align8to32(im.tobytes(), size[0], im.mode)
|
||||||
|
if exclusive_fp:
|
||||||
|
im.close()
|
||||||
|
return {"data": __data, "size": size, "format": format, "colortable": colortable}
|
||||||
|
|
||||||
|
|
||||||
if qt_is_installed:
|
if qt_is_installed:
|
||||||
|
@ -182,8 +192,8 @@ if qt_is_installed:
|
||||||
self.__data = im_data["data"]
|
self.__data = im_data["data"]
|
||||||
super().__init__(
|
super().__init__(
|
||||||
self.__data,
|
self.__data,
|
||||||
im_data["im"].size[0],
|
im_data["size"][0],
|
||||||
im_data["im"].size[1],
|
im_data["size"][1],
|
||||||
im_data["format"],
|
im_data["format"],
|
||||||
)
|
)
|
||||||
if im_data["colortable"]:
|
if im_data["colortable"]:
|
||||||
|
@ -197,11 +207,7 @@ def toqimage(im):
|
||||||
def toqpixmap(im):
|
def toqpixmap(im):
|
||||||
# # This doesn't work. For now using a dumb approach.
|
# # This doesn't work. For now using a dumb approach.
|
||||||
# im_data = _toqclass_helper(im)
|
# im_data = _toqclass_helper(im)
|
||||||
# result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
|
# result = QPixmap(im_data["size"][0], im_data["size"][1])
|
||||||
# result.loadFromData(im_data['data'])
|
# result.loadFromData(im_data["data"])
|
||||||
# Fix some strange bug that causes
|
|
||||||
if im.mode == "RGB":
|
|
||||||
im = im.convert("RGBA")
|
|
||||||
|
|
||||||
qimage = toqimage(im)
|
qimage = toqimage(im)
|
||||||
return QPixmap.fromImage(qimage)
|
return QPixmap.fromImage(qimage)
|
||||||
|
|
|
@ -225,6 +225,23 @@ if sys.platform not in ("win32", "darwin"): # unixoids
|
||||||
if shutil.which("xv"):
|
if shutil.which("xv"):
|
||||||
register(XVViewer)
|
register(XVViewer)
|
||||||
|
|
||||||
|
|
||||||
|
class IPythonViewer(Viewer):
|
||||||
|
"""The viewer for IPython frontends."""
|
||||||
|
|
||||||
|
def show_image(self, image, **options):
|
||||||
|
ipython_display(image)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from IPython.display import display as ipython_display
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
register(IPythonViewer)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
|
|
|
@ -478,7 +478,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
return dict(self.getexif())
|
return self.getexif()._get_merged_dict()
|
||||||
|
|
||||||
|
|
||||||
def _getmp(self):
|
def _getmp(self):
|
||||||
|
|
|
@ -68,7 +68,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
self.is_animated = self._n_frames > 1
|
self.is_animated = self._n_frames > 1
|
||||||
|
|
||||||
if len(self.images) > 1:
|
if len(self.images) > 1:
|
||||||
self.category = Image.CONTAINER
|
self._category = Image.CONTAINER
|
||||||
|
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
||||||
|
|
||||||
exif = self.getexif()
|
mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
|
||||||
|
if mptype.startswith("Large Thumbnail"):
|
||||||
|
exif = self.getexif().get_ifd(0x8769)
|
||||||
if 40962 in exif and 40963 in exif:
|
if 40962 in exif and 40963 in exif:
|
||||||
self._size = (exif[40962], exif[40963])
|
self._size = (exif[40962], exif[40963])
|
||||||
elif "exif" in self.info:
|
elif "exif" in self.info:
|
||||||
|
|
|
@ -66,13 +66,13 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
version = s[1]
|
version = s[1]
|
||||||
bits = s[3]
|
bits = s[3]
|
||||||
planes = s[65]
|
planes = s[65]
|
||||||
ignored_stride = i16(s, 66)
|
provided_stride = i16(s, 66)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"PCX version %s, bits %s, planes %s, stride %s",
|
"PCX version %s, bits %s, planes %s, stride %s",
|
||||||
version,
|
version,
|
||||||
bits,
|
bits,
|
||||||
planes,
|
planes,
|
||||||
ignored_stride,
|
provided_stride,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
self.info["dpi"] = i16(s, 12), i16(s, 14)
|
||||||
|
@ -110,9 +110,14 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||||
|
|
||||||
# don't trust the passed in stride. Calculate for ourselves.
|
# Don't trust the passed in stride.
|
||||||
|
# Calculate the approximate position for ourselves.
|
||||||
# CVE-2020-35653
|
# CVE-2020-35653
|
||||||
stride = (self._size[0] * bits + 7) // 8
|
stride = (self._size[0] * bits + 7) // 8
|
||||||
|
|
||||||
|
# While the specification states that this must be even,
|
||||||
|
# not all images follow this
|
||||||
|
if provided_stride != stride:
|
||||||
stride += stride % 2
|
stride += stride % 2
|
||||||
|
|
||||||
bbox = (0, 0) + self.size
|
bbox = (0, 0) + self.size
|
||||||
|
|
|
@ -968,7 +968,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.load()
|
self.load()
|
||||||
if "exif" not in self.info and "Raw profile type exif" not in self.info:
|
if "exif" not in self.info and "Raw profile type exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
return dict(self.getexif())
|
return self.getexif()._get_merged_dict()
|
||||||
|
|
||||||
def getexif(self):
|
def getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
|
@ -1186,23 +1186,21 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
# attempt to minimize storage requirements for palette images
|
# attempt to minimize storage requirements for palette images
|
||||||
if "bits" in im.encoderinfo:
|
if "bits" in im.encoderinfo:
|
||||||
# number of bits specified by user
|
# number of bits specified by user
|
||||||
colors = 1 << im.encoderinfo["bits"]
|
colors = min(1 << im.encoderinfo["bits"], 256)
|
||||||
else:
|
else:
|
||||||
# check palette contents
|
# check palette contents
|
||||||
if im.palette:
|
if im.palette:
|
||||||
colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2)
|
colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
|
||||||
else:
|
else:
|
||||||
colors = 256
|
colors = 256
|
||||||
|
|
||||||
|
if colors <= 16:
|
||||||
if colors <= 2:
|
if colors <= 2:
|
||||||
bits = 1
|
bits = 1
|
||||||
elif colors <= 4:
|
elif colors <= 4:
|
||||||
bits = 2
|
bits = 2
|
||||||
elif colors <= 16:
|
|
||||||
bits = 4
|
|
||||||
else:
|
else:
|
||||||
bits = 8
|
bits = 4
|
||||||
if bits != 8:
|
|
||||||
mode = f"{mode};{bits}"
|
mode = f"{mode};{bits}"
|
||||||
|
|
||||||
# encoder options
|
# encoder options
|
||||||
|
@ -1270,7 +1268,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
chunk(fp, cid, data)
|
chunk(fp, cid, data)
|
||||||
|
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
palette_byte_number = (2 ** bits) * 3
|
palette_byte_number = colors * 3
|
||||||
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
||||||
while len(palette_bytes) < palette_byte_number:
|
while len(palette_bytes) < palette_byte_number:
|
||||||
palette_bytes += b"\0"
|
palette_bytes += b"\0"
|
||||||
|
@ -1281,7 +1279,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
if transparency or transparency == 0:
|
if transparency or transparency == 0:
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
# limit to actual palette size
|
# limit to actual palette size
|
||||||
alpha_bytes = 2 ** bits
|
alpha_bytes = colors
|
||||||
if isinstance(transparency, bytes):
|
if isinstance(transparency, bytes):
|
||||||
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
||||||
else:
|
else:
|
||||||
|
@ -1302,7 +1300,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
else:
|
else:
|
||||||
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
|
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
|
||||||
alpha = im.im.getpalette("RGBA", "A")
|
alpha = im.im.getpalette("RGBA", "A")
|
||||||
alpha_bytes = 2 ** bits
|
alpha_bytes = colors
|
||||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||||
|
|
||||||
dpi = im.encoderinfo.get("dpi")
|
dpi = im.encoderinfo.get("dpi")
|
||||||
|
|
|
@ -1324,6 +1324,15 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if ";16L" in rawmode:
|
if ";16L" in rawmode:
|
||||||
rawmode = rawmode.replace(";16L", ";16N")
|
rawmode = rawmode.replace(";16L", ";16N")
|
||||||
|
|
||||||
|
# YCbCr images with new jpeg compression with pixels in one plane
|
||||||
|
# unpacked straight into RGB values
|
||||||
|
if (
|
||||||
|
photo == 6
|
||||||
|
and self._compression == "jpeg"
|
||||||
|
and self._planar_configuration == 1
|
||||||
|
):
|
||||||
|
rawmode = "RGB"
|
||||||
|
|
||||||
# Offset in the tile tuple is 0, we go from 0,0 to
|
# Offset in the tile tuple is 0, we go from 0,0 to
|
||||||
# w,h, and we only do this once -- eds
|
# w,h, and we only do this once -- eds
|
||||||
a = (rawmode, self._compression, False, self.tag_v2.offset)
|
a = (rawmode, self._compression, False, self.tag_v2.offset)
|
||||||
|
@ -1442,6 +1451,8 @@ def _save(im, fp, filename):
|
||||||
elif compression == "tiff_jpeg":
|
elif compression == "tiff_jpeg":
|
||||||
# OJPEG is obsolete, so use new-style JPEG compression instead
|
# OJPEG is obsolete, so use new-style JPEG compression instead
|
||||||
compression = "jpeg"
|
compression = "jpeg"
|
||||||
|
elif compression == "tiff_deflate":
|
||||||
|
compression = "tiff_adobe_deflate"
|
||||||
|
|
||||||
libtiff = WRITE_LIBTIFF or compression != "raw"
|
libtiff = WRITE_LIBTIFF or compression != "raw"
|
||||||
|
|
||||||
|
@ -1481,8 +1492,9 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
# preserve ICC profile (should also work when saving other formats
|
# preserve ICC profile (should also work when saving other formats
|
||||||
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
||||||
if "icc_profile" in im.info:
|
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
|
||||||
ifd[ICCPROFILE] = im.info["icc_profile"]
|
if icc:
|
||||||
|
ifd[ICCPROFILE] = icc
|
||||||
|
|
||||||
for key, name in [
|
for key, name in [
|
||||||
(IMAGEDESCRIPTION, "description"),
|
(IMAGEDESCRIPTION, "description"),
|
||||||
|
|
|
@ -184,6 +184,7 @@ TAGS_V2 = {
|
||||||
34665: ("ExifIFD", LONG, 1),
|
34665: ("ExifIFD", LONG, 1),
|
||||||
34675: ("ICCProfile", UNDEFINED, 1),
|
34675: ("ICCProfile", UNDEFINED, 1),
|
||||||
34853: ("GPSInfoIFD", LONG, 1),
|
34853: ("GPSInfoIFD", LONG, 1),
|
||||||
|
40965: ("InteroperabilityIFD", LONG, 1),
|
||||||
# MPInfo
|
# MPInfo
|
||||||
45056: ("MPFVersion", UNDEFINED, 1),
|
45056: ("MPFVersion", UNDEFINED, 1),
|
||||||
45057: ("NumberOfImages", LONG, 1),
|
45057: ("NumberOfImages", LONG, 1),
|
||||||
|
|
|
@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
return dict(self.getexif())
|
return self.getexif()._get_merged_dict()
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
|
@ -192,7 +192,7 @@ def _save_all(im, fp, filename):
|
||||||
r, g, b = palette[background * 3 : (background + 1) * 3]
|
r, g, b = palette[background * 3 : (background + 1) * 3]
|
||||||
background = (r, g, b, 0)
|
background = (r, g, b, 0)
|
||||||
|
|
||||||
duration = im.encoderinfo.get("duration", 0)
|
duration = im.encoderinfo.get("duration", im.info.get("duration"))
|
||||||
loop = im.encoderinfo.get("loop", 0)
|
loop = im.encoderinfo.get("loop", 0)
|
||||||
minimize_size = im.encoderinfo.get("minimize_size", False)
|
minimize_size = im.encoderinfo.get("minimize_size", False)
|
||||||
kmin = im.encoderinfo.get("kmin", None)
|
kmin = im.encoderinfo.get("kmin", None)
|
||||||
|
|
|
@ -118,6 +118,8 @@ features = {
|
||||||
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
|
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
|
||||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
|
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
|
||||||
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
||||||
|
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||||
|
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||||
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
||||||
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
||||||
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
||||||
|
@ -274,6 +276,11 @@ def pilinfo(out=None, supported_formats=True):
|
||||||
# this check is also in src/_imagingcms.c:setup_module()
|
# this check is also in src/_imagingcms.c:setup_module()
|
||||||
version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
|
version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
|
||||||
t = "compiled for" if version_static else "loaded"
|
t = "compiled for" if version_static else "loaded"
|
||||||
|
if name == "raqm":
|
||||||
|
for f in ("fribidi", "harfbuzz"):
|
||||||
|
v2 = version_feature(f)
|
||||||
|
if v2 is not None:
|
||||||
|
v += f", {f} {v2}"
|
||||||
print("---", feature, "support ok,", t, v, file=out)
|
print("---", feature, "support ok,", t, v, file=out)
|
||||||
else:
|
else:
|
||||||
print("---", feature, "support ok", file=out)
|
print("---", feature, "support ok", file=out)
|
||||||
|
|
|
@ -3973,8 +3973,6 @@ PyPath_Create(ImagingObject *self, PyObject *args);
|
||||||
extern PyObject *
|
extern PyObject *
|
||||||
PyOutline_Create(ImagingObject *self, PyObject *args);
|
PyOutline_Create(ImagingObject *self, PyObject *args);
|
||||||
|
|
||||||
extern PyObject *
|
|
||||||
PyImaging_Mapper(PyObject *self, PyObject *args);
|
|
||||||
extern PyObject *
|
extern PyObject *
|
||||||
PyImaging_MapBuffer(PyObject *self, PyObject *args);
|
PyImaging_MapBuffer(PyObject *self, PyObject *args);
|
||||||
|
|
||||||
|
@ -4030,9 +4028,6 @@ static PyMethodDef functions[] = {
|
||||||
|
|
||||||
/* Memory mapping */
|
/* Memory mapping */
|
||||||
#ifdef WITH_MAPPING
|
#ifdef WITH_MAPPING
|
||||||
#ifdef _WIN32
|
|
||||||
{"map", (PyCFunction)PyImaging_Mapper, 1},
|
|
||||||
#endif
|
|
||||||
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1},
|
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
259
src/_imagingft.c
|
@ -35,10 +35,6 @@
|
||||||
|
|
||||||
#define KEEP_PY_UNICODE
|
#define KEEP_PY_UNICODE
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(FT_LOAD_TARGET_MONO)
|
#if !defined(FT_LOAD_TARGET_MONO)
|
||||||
#define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME
|
#define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME
|
||||||
#endif
|
#endif
|
||||||
|
@ -56,7 +52,21 @@
|
||||||
} \
|
} \
|
||||||
;
|
;
|
||||||
|
|
||||||
#include "libImaging/raqm.h"
|
#ifdef HAVE_RAQM
|
||||||
|
# ifdef HAVE_RAQM_SYSTEM
|
||||||
|
# include <raqm.h>
|
||||||
|
# else
|
||||||
|
# include "thirdparty/raqm/raqm.h"
|
||||||
|
# ifdef HAVE_FRIBIDI_SYSTEM
|
||||||
|
# include <fribidi.h>
|
||||||
|
# else
|
||||||
|
# include "thirdparty/fribidi-shim/fribidi.h"
|
||||||
|
# include <hb.h>
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int have_raqm = 0;
|
||||||
|
|
||||||
#define LAYOUT_FALLBACK 0
|
#define LAYOUT_FALLBACK 0
|
||||||
#define LAYOUT_RAQM 1
|
#define LAYOUT_RAQM 1
|
||||||
|
@ -86,42 +96,6 @@ typedef struct {
|
||||||
|
|
||||||
static PyTypeObject Font_Type;
|
static PyTypeObject Font_Type;
|
||||||
|
|
||||||
typedef const char *(*t_raqm_version_string)(void);
|
|
||||||
typedef bool (*t_raqm_version_atleast)(
|
|
||||||
unsigned int major, unsigned int minor, unsigned int micro);
|
|
||||||
typedef raqm_t *(*t_raqm_create)(void);
|
|
||||||
typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, size_t len);
|
|
||||||
typedef bool (*t_raqm_set_text_utf8)(raqm_t *rq, const char *text, size_t len);
|
|
||||||
typedef bool (*t_raqm_set_par_direction)(raqm_t *rq, raqm_direction_t dir);
|
|
||||||
typedef bool (*t_raqm_set_language)(
|
|
||||||
raqm_t *rq, const char *lang, size_t start, size_t len);
|
|
||||||
typedef bool (*t_raqm_add_font_feature)(raqm_t *rq, const char *feature, int len);
|
|
||||||
typedef bool (*t_raqm_set_freetype_face)(raqm_t *rq, FT_Face face);
|
|
||||||
typedef bool (*t_raqm_layout)(raqm_t *rq);
|
|
||||||
typedef raqm_glyph_t *(*t_raqm_get_glyphs)(raqm_t *rq, size_t *length);
|
|
||||||
typedef raqm_glyph_t_01 *(*t_raqm_get_glyphs_01)(raqm_t *rq, size_t *length);
|
|
||||||
typedef void (*t_raqm_destroy)(raqm_t *rq);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void *raqm;
|
|
||||||
int version;
|
|
||||||
t_raqm_version_string version_string;
|
|
||||||
t_raqm_version_atleast version_atleast;
|
|
||||||
t_raqm_create create;
|
|
||||||
t_raqm_set_text set_text;
|
|
||||||
t_raqm_set_text_utf8 set_text_utf8;
|
|
||||||
t_raqm_set_par_direction set_par_direction;
|
|
||||||
t_raqm_set_language set_language;
|
|
||||||
t_raqm_add_font_feature add_font_feature;
|
|
||||||
t_raqm_set_freetype_face set_freetype_face;
|
|
||||||
t_raqm_layout layout;
|
|
||||||
t_raqm_get_glyphs get_glyphs;
|
|
||||||
t_raqm_get_glyphs_01 get_glyphs_01;
|
|
||||||
t_raqm_destroy destroy;
|
|
||||||
} p_raqm_func;
|
|
||||||
|
|
||||||
static p_raqm_func p_raqm;
|
|
||||||
|
|
||||||
/* round a 26.6 pixel coordinate to the nearest integer */
|
/* round a 26.6 pixel coordinate to the nearest integer */
|
||||||
#define PIXEL(x) ((((x) + 32) & -64) >> 6)
|
#define PIXEL(x) ((((x) + 32) & -64) >> 6)
|
||||||
|
|
||||||
|
@ -140,105 +114,6 @@ geterror(int code) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
setraqm(void) {
|
|
||||||
/* set the static function pointers for dynamic raqm linking */
|
|
||||||
p_raqm.raqm = NULL;
|
|
||||||
|
|
||||||
/* Microsoft needs a totally different system */
|
|
||||||
#ifndef _WIN32
|
|
||||||
p_raqm.raqm = dlopen("libraqm.so.0", RTLD_LAZY);
|
|
||||||
if (!p_raqm.raqm) {
|
|
||||||
p_raqm.raqm = dlopen("libraqm.dylib", RTLD_LAZY);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
p_raqm.raqm = LoadLibrary("libraqm");
|
|
||||||
/* MSYS */
|
|
||||||
if (!p_raqm.raqm) {
|
|
||||||
p_raqm.raqm = LoadLibrary("libraqm-0");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!p_raqm.raqm) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
p_raqm.version_string =
|
|
||||||
(t_raqm_version_string)dlsym(p_raqm.raqm, "raqm_version_string");
|
|
||||||
p_raqm.version_atleast =
|
|
||||||
(t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast");
|
|
||||||
p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create");
|
|
||||||
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
|
|
||||||
p_raqm.set_text_utf8 =
|
|
||||||
(t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8");
|
|
||||||
p_raqm.set_par_direction =
|
|
||||||
(t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction");
|
|
||||||
p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language");
|
|
||||||
p_raqm.add_font_feature =
|
|
||||||
(t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature");
|
|
||||||
p_raqm.set_freetype_face =
|
|
||||||
(t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face");
|
|
||||||
p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout");
|
|
||||||
p_raqm.destroy = (t_raqm_destroy)dlsym(p_raqm.raqm, "raqm_destroy");
|
|
||||||
if (dlsym(p_raqm.raqm, "raqm_index_to_position")) {
|
|
||||||
p_raqm.get_glyphs = (t_raqm_get_glyphs)dlsym(p_raqm.raqm, "raqm_get_glyphs");
|
|
||||||
p_raqm.version = 2;
|
|
||||||
} else {
|
|
||||||
p_raqm.version = 1;
|
|
||||||
p_raqm.get_glyphs_01 =
|
|
||||||
(t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs");
|
|
||||||
}
|
|
||||||
if (dlerror() ||
|
|
||||||
!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 &&
|
|
||||||
p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature &&
|
|
||||||
p_raqm.set_freetype_face && p_raqm.layout &&
|
|
||||||
(p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) {
|
|
||||||
dlclose(p_raqm.raqm);
|
|
||||||
p_raqm.raqm = NULL;
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
p_raqm.version_string =
|
|
||||||
(t_raqm_version_string)GetProcAddress(p_raqm.raqm, "raqm_version_string");
|
|
||||||
p_raqm.version_atleast =
|
|
||||||
(t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast");
|
|
||||||
p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create");
|
|
||||||
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
|
|
||||||
p_raqm.set_text_utf8 =
|
|
||||||
(t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8");
|
|
||||||
p_raqm.set_par_direction =
|
|
||||||
(t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction");
|
|
||||||
p_raqm.set_language =
|
|
||||||
(t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language");
|
|
||||||
p_raqm.add_font_feature =
|
|
||||||
(t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature");
|
|
||||||
p_raqm.set_freetype_face =
|
|
||||||
(t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face");
|
|
||||||
p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout");
|
|
||||||
p_raqm.destroy = (t_raqm_destroy)GetProcAddress(p_raqm.raqm, "raqm_destroy");
|
|
||||||
if (GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) {
|
|
||||||
p_raqm.get_glyphs =
|
|
||||||
(t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs");
|
|
||||||
p_raqm.version = 2;
|
|
||||||
} else {
|
|
||||||
p_raqm.version = 1;
|
|
||||||
p_raqm.get_glyphs_01 =
|
|
||||||
(t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs");
|
|
||||||
}
|
|
||||||
if (!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 &&
|
|
||||||
p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature &&
|
|
||||||
p_raqm.set_freetype_face && p_raqm.layout &&
|
|
||||||
(p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) {
|
|
||||||
FreeLibrary(p_raqm.raqm);
|
|
||||||
p_raqm.raqm = NULL;
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
/* create a font object from a file name and a size (in pixels) */
|
/* create a font object from a file name and a size (in pixels) */
|
||||||
|
@ -346,6 +221,8 @@ font_getchar(PyObject *string, int index, FT_ULong *char_out) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_RAQM
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_raqm(
|
text_layout_raqm(
|
||||||
PyObject *string,
|
PyObject *string,
|
||||||
|
@ -359,10 +236,9 @@ text_layout_raqm(
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq;
|
||||||
raqm_glyph_t *glyphs = NULL;
|
raqm_glyph_t *glyphs = NULL;
|
||||||
raqm_glyph_t_01 *glyphs_01 = NULL;
|
|
||||||
raqm_direction_t direction;
|
raqm_direction_t direction;
|
||||||
|
|
||||||
rq = (*p_raqm.create)();
|
rq = raqm_create();
|
||||||
if (rq == NULL) {
|
if (rq == NULL) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
|
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
|
||||||
goto failed;
|
goto failed;
|
||||||
|
@ -376,14 +252,14 @@ text_layout_raqm(
|
||||||
and raqm fails with empty strings */
|
and raqm fails with empty strings */
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
int set_text = (*p_raqm.set_text)(rq, text, size);
|
int set_text = raqm_set_text(rq, text, size);
|
||||||
PyMem_Free(text);
|
PyMem_Free(text);
|
||||||
if (!set_text) {
|
if (!set_text) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
if (lang) {
|
if (lang) {
|
||||||
if (!(*p_raqm.set_language)(rq, lang, start, size)) {
|
if (!raqm_set_language(rq, lang, start, size)) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
@ -401,12 +277,12 @@ text_layout_raqm(
|
||||||
direction = RAQM_DIRECTION_LTR;
|
direction = RAQM_DIRECTION_LTR;
|
||||||
} else if (strcmp(dir, "ttb") == 0) {
|
} else if (strcmp(dir, "ttb") == 0) {
|
||||||
direction = RAQM_DIRECTION_TTB;
|
direction = RAQM_DIRECTION_TTB;
|
||||||
if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) {
|
#if !defined(RAQM_VERSION_ATLEAST) || !RAQM_VERSION_ATLEAST(0, 7, 0)
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError,
|
||||||
"libraqm 0.7 or greater required for 'ttb' direction");
|
"libraqm 0.7 or greater required for 'ttb' direction");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
#endif
|
||||||
} else {
|
} else {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
|
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
|
||||||
|
@ -414,7 +290,7 @@ text_layout_raqm(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(*p_raqm.set_par_direction)(rq, direction)) {
|
if (!raqm_set_par_direction(rq, direction)) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
@ -446,38 +322,29 @@ text_layout_raqm(
|
||||||
feature = PyBytes_AS_STRING(bytes);
|
feature = PyBytes_AS_STRING(bytes);
|
||||||
size = PyBytes_GET_SIZE(bytes);
|
size = PyBytes_GET_SIZE(bytes);
|
||||||
}
|
}
|
||||||
if (!(*p_raqm.add_font_feature)(rq, feature, size)) {
|
if (!raqm_add_font_feature(rq, feature, size)) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(*p_raqm.set_freetype_face)(rq, self->face)) {
|
if (!raqm_set_freetype_face(rq, self->face)) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(*p_raqm.layout)(rq)) {
|
if (!raqm_layout(rq)) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_raqm.version == 1) {
|
glyphs = raqm_get_glyphs(rq, &count);
|
||||||
glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count);
|
|
||||||
if (glyphs_01 == NULL) {
|
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
|
||||||
count = 0;
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
} else { /* version == 2 */
|
|
||||||
glyphs = (*p_raqm.get_glyphs)(rq, &count);
|
|
||||||
if (glyphs == NULL) {
|
if (glyphs == NULL) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
||||||
count = 0;
|
count = 0;
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(*glyph_info) = PyMem_New(GlyphInfo, count);
|
(*glyph_info) = PyMem_New(GlyphInfo, count);
|
||||||
if ((*glyph_info) == NULL) {
|
if ((*glyph_info) == NULL) {
|
||||||
|
@ -486,16 +353,6 @@ text_layout_raqm(
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_raqm.version == 1) {
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
(*glyph_info)[i].index = glyphs_01[i].index;
|
|
||||||
(*glyph_info)[i].x_offset = glyphs_01[i].x_offset;
|
|
||||||
(*glyph_info)[i].x_advance = glyphs_01[i].x_advance;
|
|
||||||
(*glyph_info)[i].y_offset = glyphs_01[i].y_offset;
|
|
||||||
(*glyph_info)[i].y_advance = glyphs_01[i].y_advance;
|
|
||||||
(*glyph_info)[i].cluster = glyphs_01[i].cluster;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
(*glyph_info)[i].index = glyphs[i].index;
|
(*glyph_info)[i].index = glyphs[i].index;
|
||||||
(*glyph_info)[i].x_offset = glyphs[i].x_offset;
|
(*glyph_info)[i].x_offset = glyphs[i].x_offset;
|
||||||
|
@ -504,13 +361,14 @@ text_layout_raqm(
|
||||||
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
|
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
|
||||||
(*glyph_info)[i].cluster = glyphs[i].cluster;
|
(*glyph_info)[i].cluster = glyphs[i].cluster;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
failed:
|
failed:
|
||||||
(*p_raqm.destroy)(rq);
|
raqm_destroy(rq);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_fallback(
|
text_layout_fallback(
|
||||||
PyObject *string,
|
PyObject *string,
|
||||||
|
@ -606,11 +464,13 @@ text_layout(
|
||||||
int mask,
|
int mask,
|
||||||
int color) {
|
int color) {
|
||||||
size_t count;
|
size_t count;
|
||||||
|
#ifdef HAVE_RAQM
|
||||||
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||||
count = text_layout_raqm(
|
count = text_layout_raqm(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color);
|
string, self, dir, features, lang, glyph_info, mask, color);
|
||||||
} else {
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
count = text_layout_fallback(
|
count = text_layout_fallback(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color);
|
string, self, dir, features, lang, glyph_info, mask, color);
|
||||||
}
|
}
|
||||||
|
@ -1490,12 +1350,51 @@ setup_module(PyObject *m) {
|
||||||
v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch);
|
v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch);
|
||||||
PyDict_SetItemString(d, "freetype2_version", v);
|
PyDict_SetItemString(d, "freetype2_version", v);
|
||||||
|
|
||||||
setraqm();
|
#ifdef HAVE_RAQM
|
||||||
v = PyBool_FromLong(!!p_raqm.raqm);
|
#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM)
|
||||||
|
have_raqm = 1;
|
||||||
|
#else
|
||||||
|
load_fribidi();
|
||||||
|
have_raqm = !!p_fribidi;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
have_raqm = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* if we have Raqm, we have all three (but possibly no version info) */
|
||||||
|
v = PyBool_FromLong(have_raqm);
|
||||||
PyDict_SetItemString(d, "HAVE_RAQM", v);
|
PyDict_SetItemString(d, "HAVE_RAQM", v);
|
||||||
if (p_raqm.version_string) {
|
PyDict_SetItemString(d, "HAVE_FRIBIDI", v);
|
||||||
PyDict_SetItemString(
|
PyDict_SetItemString(d, "HAVE_HARFBUZZ", v);
|
||||||
d, "raqm_version", PyUnicode_FromString(p_raqm.version_string()));
|
if (have_raqm) {
|
||||||
|
#ifdef RAQM_VERSION_MAJOR
|
||||||
|
v = PyUnicode_FromString(raqm_version_string());
|
||||||
|
#else
|
||||||
|
v = Py_None;
|
||||||
|
#endif
|
||||||
|
PyDict_SetItemString(d, "raqm_version", v);
|
||||||
|
|
||||||
|
#ifdef FRIBIDI_MAJOR_VERSION
|
||||||
|
{
|
||||||
|
const char *a = strchr(fribidi_version_info, ')');
|
||||||
|
const char *b = strchr(fribidi_version_info, '\n');
|
||||||
|
if (a && b && a + 2 < b) {
|
||||||
|
v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2));
|
||||||
|
} else {
|
||||||
|
v = Py_None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
v = Py_None;
|
||||||
|
#endif
|
||||||
|
PyDict_SetItemString(d, "fribidi_version", v);
|
||||||
|
|
||||||
|
#ifdef HB_VERSION_STRING
|
||||||
|
v = PyUnicode_FromString(hb_version_string());
|
||||||
|
#else
|
||||||
|
v = Py_None;
|
||||||
|
#endif
|
||||||
|
PyDict_SetItemString(d, "harfbuzz_version", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -724,8 +724,8 @@ ImagingDrawRectangle(
|
||||||
for (i = 0; i < width; i++) {
|
for (i = 0; i < width; i++) {
|
||||||
draw->hline(im, x0, y0 + i, x1, ink);
|
draw->hline(im, x0, y0 + i, x1, ink);
|
||||||
draw->hline(im, x0, y1 - i, x1, ink);
|
draw->hline(im, x0, y1 - i, x1, ink);
|
||||||
draw->line(im, x1 - i, y0, x1 - i, y1, ink);
|
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
|
||||||
draw->line(im, x0 + i, y1, x0 + i, y0, ink);
|
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,9 +76,22 @@ ImagingFillLinearGradient(const char *mode) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (im->image8) {
|
||||||
for (y = 0; y < 256; y++) {
|
for (y = 0; y < 256; y++) {
|
||||||
memset(im->image8[y], (unsigned char)y, 256);
|
memset(im->image8[y], (unsigned char)y, 256);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
int x;
|
||||||
|
for (y = 0; y < 256; y++) {
|
||||||
|
for (x = 0; x < 256; x++) {
|
||||||
|
if (im->type == IMAGING_TYPE_FLOAT32) {
|
||||||
|
IMAGING_PIXEL_FLOAT32(im, x, y) = y;
|
||||||
|
} else {
|
||||||
|
IMAGING_PIXEL_INT32(im, x, y) = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
|
@ -103,9 +116,16 @@ ImagingFillRadialGradient(const char *mode) {
|
||||||
d = (int)sqrt(
|
d = (int)sqrt(
|
||||||
(double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
|
(double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
|
||||||
if (d >= 255) {
|
if (d >= 255) {
|
||||||
im->image8[y][x] = 255;
|
d = 255;
|
||||||
} else {
|
}
|
||||||
|
if (im->image8) {
|
||||||
im->image8[y][x] = d;
|
im->image8[y][x] = d;
|
||||||
|
} else {
|
||||||
|
if (im->type == IMAGING_TYPE_FLOAT32) {
|
||||||
|
IMAGING_PIXEL_FLOAT32(im, x, y) = d;
|
||||||
|
} else {
|
||||||
|
IMAGING_PIXEL_INT32(im, x, y) = d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -789,7 +789,7 @@ resort_distance_tables(
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
build_distance_tables(
|
build_distance_tables(
|
||||||
uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
|
uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
|
||||||
uint32_t i, j;
|
uint32_t i, j;
|
||||||
|
@ -811,7 +811,6 @@ build_distance_tables(
|
||||||
sizeof(uint32_t *),
|
sizeof(uint32_t *),
|
||||||
_sort_ulong_ptr_keys);
|
_sort_ulong_ptr_keys);
|
||||||
}
|
}
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -1373,9 +1372,7 @@ quantize(
|
||||||
goto error_6;
|
goto error_6;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) {
|
build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries);
|
||||||
goto error_7;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!map_image_pixels_from_median_box(
|
if (!map_image_pixels_from_median_box(
|
||||||
pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) {
|
pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) {
|
||||||
|
@ -1580,9 +1577,7 @@ quantize2(
|
||||||
goto error_3;
|
goto error_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) {
|
build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels);
|
||||||
goto error_4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!map_image_pixels(
|
if (!map_image_pixels(
|
||||||
pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) {
|
pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) {
|
||||||
|
|
|
@ -56,7 +56,7 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) {
|
||||||
dump_state(state);
|
dump_state(state);
|
||||||
|
|
||||||
if (state->loc > state->eof) {
|
if (state->loc > state->eof) {
|
||||||
TIFFError("_tiffReadProc", "Invalid Read at loc %d, eof: %d", state->loc, state->eof);
|
TIFFError("_tiffReadProc", "Invalid Read at loc %llu, eof: %llu", state->loc, state->eof);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
|
to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
|
||||||
|
@ -213,24 +213,63 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
_decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16 planarconfig, ImagingShuffler *unpackers) {
|
||||||
// To avoid dealing with YCbCr subsampling, let libtiff handle it
|
// if number of bands is 1, there is no difference with contig case
|
||||||
|
if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) {
|
||||||
|
uint16 bits_per_sample = 8;
|
||||||
|
|
||||||
|
TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
|
||||||
|
if (bits_per_sample != 8 && bits_per_sample != 16) {
|
||||||
|
TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample));
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll pick appropriate set of unpackers depending on planar_configuration
|
||||||
|
// It does not matter if data is RGB(A), CMYK or LUV really,
|
||||||
|
// we just copy it plane by plane
|
||||||
|
unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL);
|
||||||
|
unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL);
|
||||||
|
unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL);
|
||||||
|
unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL);
|
||||||
|
|
||||||
|
return im->bands;
|
||||||
|
} else {
|
||||||
|
unpackers[0] = state->shuffle;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
|
// To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it
|
||||||
// Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle
|
// Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle
|
||||||
// all of the conversion. Metadata read from the TIFFRGBAImage could
|
// all of the conversion. Metadata read from the TIFFRGBAImage could
|
||||||
// be different from the metadata that the base tiff returns.
|
// be different from the metadata that the base tiff returns.
|
||||||
|
|
||||||
INT32 strip_row;
|
INT32 current_row;
|
||||||
UINT8 *new_data;
|
UINT8 *new_data;
|
||||||
UINT32 rows_per_strip, row_byte_size, rows_to_read;
|
UINT32 rows_per_block, row_byte_size, rows_to_read;
|
||||||
int ret;
|
int ret;
|
||||||
TIFFRGBAImage img;
|
TIFFRGBAImage img;
|
||||||
char emsg[1024] = "";
|
char emsg[1024] = "";
|
||||||
|
|
||||||
ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
// Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call
|
||||||
if (ret != 1) {
|
// Let's select smaller block size. Multiplying image width by (tile length OR rows per strip)
|
||||||
rows_per_strip = state->ysize;
|
// gives us manageable block size in pixels
|
||||||
|
if (TIFFIsTiled(tiff)) {
|
||||||
|
ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block);
|
||||||
}
|
}
|
||||||
TRACE(("RowsPerStrip: %u \n", rows_per_strip));
|
else {
|
||||||
|
ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != 1) {
|
||||||
|
rows_per_block = state->ysize;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE(("RowsPerBlock: %u \n", rows_per_block));
|
||||||
|
|
||||||
if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) {
|
if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) {
|
||||||
TRACE(("Decode error, msg: %s", emsg));
|
TRACE(("Decode error, msg: %s", emsg));
|
||||||
|
@ -250,69 +289,73 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
state->ysize,
|
state->ysize,
|
||||||
img.height));
|
img.height));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
goto decodeycbcr_err;
|
goto decodergba_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* overflow check for row byte size */
|
/* overflow check for row byte size */
|
||||||
if (INT_MAX / 4 < img.width) {
|
if (INT_MAX / 4 < img.width) {
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
goto decodeycbcr_err;
|
goto decodergba_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TiffRGBAImages are 32bits/pixel.
|
// TiffRGBAImages are 32bits/pixel.
|
||||||
row_byte_size = img.width * 4;
|
row_byte_size = img.width * 4;
|
||||||
|
|
||||||
/* overflow check for realloc */
|
/* overflow check for realloc */
|
||||||
if (INT_MAX / row_byte_size < rows_per_strip) {
|
if (INT_MAX / row_byte_size < rows_per_block) {
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
goto decodeycbcr_err;
|
goto decodergba_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->bytes = rows_per_strip * row_byte_size;
|
state->bytes = rows_per_block * row_byte_size;
|
||||||
|
|
||||||
TRACE(("StripSize: %d \n", state->bytes));
|
TRACE(("BlockSize: %d \n", state->bytes));
|
||||||
|
|
||||||
/* realloc to fit whole strip */
|
/* realloc to fit whole strip */
|
||||||
/* malloc check above */
|
/* malloc check above */
|
||||||
new_data = realloc(state->buffer, state->bytes);
|
new_data = realloc(state->buffer, state->bytes);
|
||||||
if (!new_data) {
|
if (!new_data) {
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
goto decodeycbcr_err;
|
goto decodergba_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->buffer = new_data;
|
state->buffer = new_data;
|
||||||
|
|
||||||
for (; state->y < state->ysize; state->y += rows_per_strip) {
|
for (; state->y < state->ysize; state->y += rows_per_block) {
|
||||||
img.row_offset = state->y;
|
img.row_offset = state->y;
|
||||||
rows_to_read = min(rows_per_strip, img.height - state->y);
|
rows_to_read = min(rows_per_block, img.height - state->y);
|
||||||
|
|
||||||
if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) {
|
if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) {
|
||||||
TRACE(("Decode Error, y: %d\n", state->y));
|
TRACE(("Decode Error, y: %d\n", state->y));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
goto decodeycbcr_err;
|
goto decodergba_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if WORDS_BIGENDIAN
|
||||||
|
TIFFSwabArrayOfLong((UINT32 *)state->buffer, img.width * rows_to_read);
|
||||||
|
#endif
|
||||||
|
|
||||||
TRACE(("Decoded strip for row %d \n", state->y));
|
TRACE(("Decoded strip for row %d \n", state->y));
|
||||||
|
|
||||||
// iterate over each row in the strip and stuff data into image
|
// iterate over each row in the strip and stuff data into image
|
||||||
for (strip_row = 0;
|
for (current_row = 0;
|
||||||
strip_row < min((INT32)rows_per_strip, state->ysize - state->y);
|
current_row < min((INT32)rows_per_block, state->ysize - state->y);
|
||||||
strip_row++) {
|
current_row++) {
|
||||||
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
|
TRACE(("Writing data into line %d ; \n", state->y + current_row));
|
||||||
|
|
||||||
// UINT8 * bbb = state->buffer + strip_row * (state->bytes /
|
// UINT8 * bbb = state->buffer + current_row * (state->bytes /
|
||||||
// rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0],
|
// rows_per_block); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0],
|
||||||
// ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
// ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
||||||
|
|
||||||
state->shuffle(
|
state->shuffle(
|
||||||
(UINT8 *)im->image[state->y + state->yoff + strip_row] +
|
(UINT8 *)im->image[state->y + state->yoff + current_row] +
|
||||||
state->xoff * im->pixelsize,
|
state->xoff * im->pixelsize,
|
||||||
state->buffer + strip_row * row_byte_size,
|
state->buffer + current_row * row_byte_size,
|
||||||
state->xsize);
|
state->xsize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeycbcr_err:
|
decodergba_err:
|
||||||
TIFFRGBAImageEnd(&img);
|
TIFFRGBAImageEnd(&img);
|
||||||
if (state->errcode != 0) {
|
if (state->errcode != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -321,41 +364,154 @@ decodeycbcr_err:
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
|
||||||
INT32 strip_row;
|
INT32 x, y, tile_y, current_tile_length, current_tile_width;
|
||||||
|
UINT32 tile_width, tile_length;
|
||||||
|
tsize_t tile_bytes_size, row_byte_size;
|
||||||
UINT8 *new_data;
|
UINT8 *new_data;
|
||||||
UINT32 rows_per_strip, row_byte_size;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
tile_bytes_size = TIFFTileSize(tiff);
|
||||||
if (ret != 1) {
|
|
||||||
rows_per_strip = state->ysize;
|
if (tile_bytes_size == 0) {
|
||||||
|
TRACE(("Decode Error, Can not calculate TileSize\n"));
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
TRACE(("RowsPerStrip: %u \n", rows_per_strip));
|
|
||||||
|
|
||||||
// We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
|
row_byte_size = TIFFTileRowSize(tiff);
|
||||||
row_byte_size = (state->xsize * state->bits + 7) / 8;
|
|
||||||
|
if (row_byte_size == 0 || row_byte_size > tile_bytes_size) {
|
||||||
|
TRACE(("Decode Error, Can not calculate TileRowSize\n"));
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* overflow check for realloc */
|
/* overflow check for realloc */
|
||||||
if (INT_MAX / row_byte_size < rows_per_strip) {
|
if (tile_bytes_size > INT_MAX - 1) {
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->bytes = rows_per_strip * row_byte_size;
|
TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
|
||||||
|
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
|
||||||
|
|
||||||
|
if (tile_width > INT_MAX || tile_length > INT_MAX) {
|
||||||
|
// state->x and state->y are ints
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) {
|
||||||
|
// If the tile size as expected by LibTiff isn't what we're expecting, abort.
|
||||||
|
// man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a
|
||||||
|
// call to TIFFReadTile ...
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->bytes = tile_bytes_size;
|
||||||
|
|
||||||
|
TRACE(("TIFFTileSize: %d\n", state->bytes));
|
||||||
|
|
||||||
|
/* realloc to fit whole tile */
|
||||||
|
/* malloc check above */
|
||||||
|
new_data = realloc(state->buffer, state->bytes);
|
||||||
|
if (!new_data) {
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
state->buffer = new_data;
|
||||||
|
|
||||||
|
for (y = state->yoff; y < state->ysize; y += tile_length) {
|
||||||
|
int plane;
|
||||||
|
for (plane = 0; plane < planes; plane++) {
|
||||||
|
ImagingShuffler shuffler = unpackers[plane];
|
||||||
|
for (x = state->xoff; x < state->xsize; x += tile_width) {
|
||||||
|
/* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions
|
||||||
|
have a different view of the size of the tiff than we're getting from
|
||||||
|
other functions. So, we need to check here.
|
||||||
|
*/
|
||||||
|
if (!TIFFCheckTile(tiff, x, y, 0, plane)) {
|
||||||
|
TRACE(("Check Tile Error, Tile at %dx%d\n", x, y));
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) {
|
||||||
|
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE(("Read tile at %dx%d; \n\n", x, y));
|
||||||
|
|
||||||
|
current_tile_width = min((INT32) tile_width, state->xsize - x);
|
||||||
|
current_tile_length = min((INT32) tile_length, state->ysize - y);
|
||||||
|
// iterate over each line in the tile and stuff data into image
|
||||||
|
for (tile_y = 0; tile_y < current_tile_length; tile_y++) {
|
||||||
|
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
|
||||||
|
|
||||||
|
// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
|
||||||
|
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
||||||
|
|
||||||
|
shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
|
||||||
|
state->buffer + tile_y * row_byte_size,
|
||||||
|
current_tile_width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
|
||||||
|
INT32 strip_row = 0;
|
||||||
|
UINT8 *new_data;
|
||||||
|
UINT32 rows_per_strip;
|
||||||
|
int ret;
|
||||||
|
tsize_t strip_size, row_byte_size;
|
||||||
|
|
||||||
|
ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
||||||
|
if (ret != 1 || rows_per_strip==(UINT32)(-1)) {
|
||||||
|
rows_per_strip = state->ysize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows_per_strip > INT_MAX) {
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE(("RowsPerStrip: %u\n", rows_per_strip));
|
||||||
|
|
||||||
|
strip_size = TIFFStripSize(tiff);
|
||||||
|
if (strip_size > INT_MAX - 1) {
|
||||||
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strip_size > ((state->xsize * state->bits / planes + 7) / 8) * rows_per_strip) {
|
||||||
|
// If the strip size as expected by LibTiff isn't what we're expecting, abort.
|
||||||
|
// man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a
|
||||||
|
// call to TIFFReadEncodedStrip ...
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->bytes = strip_size;
|
||||||
|
|
||||||
TRACE(("StripSize: %d \n", state->bytes));
|
TRACE(("StripSize: %d \n", state->bytes));
|
||||||
|
|
||||||
if (TIFFStripSize(tiff) > state->bytes) {
|
row_byte_size = TIFFScanlineSize(tiff);
|
||||||
// If the strip size as expected by LibTiff isn't what we're expecting, abort.
|
|
||||||
// man: TIFFStripSize returns the equivalent size for a strip of data as it
|
|
||||||
// would be returned in a
|
|
||||||
// call to TIFFReadEncodedStrip ...
|
|
||||||
|
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
if (row_byte_size == 0 || row_byte_size > strip_size) {
|
||||||
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TRACE(("RowsByteSize: %u \n", row_byte_size));
|
||||||
|
|
||||||
/* realloc to fit whole strip */
|
/* realloc to fit whole strip */
|
||||||
/* malloc check above */
|
/* malloc check above */
|
||||||
new_data = realloc(state->buffer, state->bytes);
|
new_data = realloc(state->buffer, state->bytes);
|
||||||
|
@ -367,11 +523,10 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
state->buffer = new_data;
|
state->buffer = new_data;
|
||||||
|
|
||||||
for (; state->y < state->ysize; state->y += rows_per_strip) {
|
for (; state->y < state->ysize; state->y += rows_per_strip) {
|
||||||
if (TIFFReadEncodedStrip(
|
int plane;
|
||||||
tiff,
|
for (plane = 0; plane < planes; plane++) {
|
||||||
TIFFComputeStrip(tiff, state->y, 0),
|
ImagingShuffler shuffler = unpackers[plane];
|
||||||
(tdata_t)state->buffer,
|
if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) {
|
||||||
-1) == -1) {
|
|
||||||
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
|
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -381,21 +536,22 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) {
|
||||||
|
|
||||||
// iterate over each row in the strip and stuff data into image
|
// iterate over each row in the strip and stuff data into image
|
||||||
for (strip_row = 0;
|
for (strip_row = 0;
|
||||||
strip_row < min((INT32)rows_per_strip, state->ysize - state->y);
|
strip_row < min((INT32) rows_per_strip, state->ysize - state->y);
|
||||||
strip_row++) {
|
strip_row++) {
|
||||||
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
|
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
|
||||||
|
|
||||||
// UINT8 * bbb = state->buffer + strip_row * (state->bytes /
|
// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
|
||||||
// rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0],
|
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
||||||
// ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
|
||||||
|
|
||||||
state->shuffle(
|
shuffler(
|
||||||
(UINT8 *)im->image[state->y + state->yoff + strip_row] +
|
(UINT8*) im->image[state->y + state->yoff + strip_row] +
|
||||||
state->xoff * im->pixelsize,
|
state->xoff * im->pixelsize,
|
||||||
state->buffer + strip_row * row_byte_size,
|
state->buffer + strip_row * row_byte_size,
|
||||||
state->xsize);
|
state->xsize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +563,13 @@ ImagingLibTiffDecode(
|
||||||
char *mode = "r";
|
char *mode = "r";
|
||||||
TIFF *tiff;
|
TIFF *tiff;
|
||||||
uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR
|
uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR
|
||||||
int isYCbCr = 0;
|
uint16 compression;
|
||||||
|
int readAsRGBA = 0;
|
||||||
|
uint16 planarconfig = 0;
|
||||||
|
int planes = 1;
|
||||||
|
ImagingShuffler unpackers[4];
|
||||||
|
|
||||||
|
memset(unpackers, 0, sizeof(ImagingShuffler) * 4);
|
||||||
|
|
||||||
/* buffer is the encoded file, bytes is the length of the encoded file */
|
/* buffer is the encoded file, bytes is the length of the encoded file */
|
||||||
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
|
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
|
||||||
|
@ -502,134 +664,64 @@ ImagingLibTiffDecode(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
|
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
|
||||||
isYCbCr = photometric == PHOTOMETRIC_YCBCR;
|
TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression);
|
||||||
|
TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig);
|
||||||
|
|
||||||
|
// Dealing with YCbCr images is complicated in case if subsampling
|
||||||
|
// Let LibTiff read them as RGBA
|
||||||
|
readAsRGBA = photometric == PHOTOMETRIC_YCBCR;
|
||||||
|
|
||||||
|
if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) {
|
||||||
|
// If using new JPEG compression, let libjpeg do RGB convertion for performance reasons
|
||||||
|
TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
|
||||||
|
readAsRGBA = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readAsRGBA) {
|
||||||
|
_decodeAsRGBA(im, state, tiff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers);
|
||||||
|
if (planes <= 0) {
|
||||||
|
goto decode_err;
|
||||||
|
}
|
||||||
|
|
||||||
if (TIFFIsTiled(tiff)) {
|
if (TIFFIsTiled(tiff)) {
|
||||||
INT32 x, y, tile_y;
|
_decodeTile(im, state, tiff, planes, unpackers);
|
||||||
UINT32 tile_width, tile_length, current_tile_length, current_line,
|
}
|
||||||
current_tile_width, row_byte_size;
|
else {
|
||||||
UINT8 *new_data;
|
_decodeStrip(im, state, tiff, planes, unpackers);
|
||||||
|
|
||||||
TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width);
|
|
||||||
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length);
|
|
||||||
|
|
||||||
/* overflow check for row_byte_size calculation */
|
|
||||||
if ((UINT32)INT_MAX / state->bits < tile_width) {
|
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isYCbCr) {
|
if (!state->errcode) {
|
||||||
row_byte_size = tile_width * 4;
|
// Check if raw mode was RGBa and it was stored on separate planes
|
||||||
/* sanity check, we use this value in shuffle below */
|
// so we have to convert it to RGBA
|
||||||
if (im->pixelsize != 4) {
|
if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
uint16 extrasamples;
|
||||||
goto decode_err;
|
uint16* sampleinfo;
|
||||||
}
|
ImagingShuffler shuffle;
|
||||||
} else {
|
INT32 y;
|
||||||
// We could use TIFFTileSize, but for YCbCr data it returns subsampled data
|
|
||||||
// size
|
|
||||||
row_byte_size = (tile_width * state->bits + 7) / 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* overflow check for realloc */
|
TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
|
||||||
if (INT_MAX / row_byte_size < tile_length) {
|
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
state->bytes = row_byte_size * tile_length;
|
if (extrasamples >= 1 &&
|
||||||
|
(sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)
|
||||||
|
) {
|
||||||
|
shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);
|
||||||
|
|
||||||
if (TIFFTileSize(tiff) > state->bytes) {
|
for (y = state->yoff; y < state->ysize; y++) {
|
||||||
// If the strip size as expected by LibTiff isn't what we're expecting,
|
UINT8* ptr = (UINT8*) im->image[y + state->yoff] +
|
||||||
// abort.
|
state->xoff * im->pixelsize;
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
shuffle(ptr, ptr, state->xsize);
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* realloc to fit whole tile */
|
|
||||||
/* malloc check above */
|
|
||||||
new_data = realloc(state->buffer, state->bytes);
|
|
||||||
if (!new_data) {
|
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
state->buffer = new_data;
|
|
||||||
|
|
||||||
TRACE(("TIFFTileSize: %d\n", state->bytes));
|
|
||||||
|
|
||||||
for (y = state->yoff; y < state->ysize; y += tile_length) {
|
|
||||||
for (x = state->xoff; x < state->xsize; x += tile_width) {
|
|
||||||
/* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions
|
|
||||||
have a different view of the size of the tiff than we're getting from
|
|
||||||
other functions. So, we need to check here.
|
|
||||||
*/
|
|
||||||
if (!TIFFCheckTile(tiff, x, y, 0, 0)) {
|
|
||||||
TRACE(("Check Tile Error, Tile at %dx%d\n", x, y));
|
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
if (isYCbCr) {
|
|
||||||
/* To avoid dealing with YCbCr subsampling, let libtiff handle it */
|
|
||||||
if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) {
|
|
||||||
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
|
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) {
|
|
||||||
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
|
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
|
||||||
goto decode_err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TRACE(("Read tile at %dx%d; \n\n", x, y));
|
|
||||||
|
|
||||||
current_tile_width = min((INT32)tile_width, state->xsize - x);
|
|
||||||
current_tile_length = min((INT32)tile_length, state->ysize - y);
|
|
||||||
// iterate over each line in the tile and stuff data into image
|
|
||||||
for (tile_y = 0; tile_y < current_tile_length; tile_y++) {
|
|
||||||
TRACE(
|
|
||||||
("Writing tile data at %dx%d using tile_width: %d; \n",
|
|
||||||
tile_y + y,
|
|
||||||
x,
|
|
||||||
current_tile_width));
|
|
||||||
|
|
||||||
// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
|
|
||||||
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1],
|
|
||||||
// ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
|
|
||||||
/*
|
|
||||||
* For some reason the TIFFReadRGBATile() function
|
|
||||||
* chooses the lower left corner as the origin.
|
|
||||||
* Vertically mirror by shuffling the scanlines
|
|
||||||
* backwards
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (isYCbCr) {
|
|
||||||
current_line = tile_length - tile_y - 1;
|
|
||||||
} else {
|
|
||||||
current_line = tile_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
state->shuffle(
|
|
||||||
(UINT8 *)im->image[tile_y + y] + x * im->pixelsize,
|
|
||||||
state->buffer + current_line * row_byte_size,
|
|
||||||
current_tile_width);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (!isYCbCr) {
|
|
||||||
_decodeStrip(im, state, tiff);
|
|
||||||
} else {
|
|
||||||
_decodeStripYCbCr(im, state, tiff);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decode_err:
|
decode_err:
|
||||||
TIFFClose(tiff);
|
TIFFClose(tiff);
|
||||||
TRACE(("Done Decoding, Returning \n"));
|
TRACE(("Done Decoding, Returning \n"));
|
||||||
// Returning -1 here to force ImageFile.load to break, rather than
|
// Returning -1 here to force ImageFile.load to break, rather than
|
||||||
|
|
|
@ -1363,6 +1363,94 @@ band3I(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band016B(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 0 only, big endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[0] = in[0];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band116B(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 1 only, big endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[1] = in[0];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band216B(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 2 only, big endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[2] = in[0];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band316B(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 3 only, big endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[3] = in[0];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band016L(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 0 only, little endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[0] = in[1];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band116L(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 1 only, little endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[1] = in[1];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band216L(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 2 only, little endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[2] = in[1];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
band316L(UINT8* out, const UINT8* in, int pixels)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
/* band 3 only, little endian */
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[3] = in[1];
|
||||||
|
out += 4; in += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
const char *mode;
|
const char *mode;
|
||||||
const char *rawmode;
|
const char *rawmode;
|
||||||
|
@ -1448,6 +1536,12 @@ static struct {
|
||||||
{"RGB", "R", 8, band0},
|
{"RGB", "R", 8, band0},
|
||||||
{"RGB", "G", 8, band1},
|
{"RGB", "G", 8, band1},
|
||||||
{"RGB", "B", 8, band2},
|
{"RGB", "B", 8, band2},
|
||||||
|
{"RGB", "R;16L", 16, band016L},
|
||||||
|
{"RGB", "G;16L", 16, band116L},
|
||||||
|
{"RGB", "B;16L", 16, band216L},
|
||||||
|
{"RGB", "R;16B", 16, band016B},
|
||||||
|
{"RGB", "G;16B", 16, band116B},
|
||||||
|
{"RGB", "B;16B", 16, band216B},
|
||||||
|
|
||||||
/* true colour w. alpha */
|
/* true colour w. alpha */
|
||||||
{"RGBA", "LA", 16, unpackRGBALA},
|
{"RGBA", "LA", 16, unpackRGBALA},
|
||||||
|
@ -1476,17 +1570,42 @@ static struct {
|
||||||
{"RGBA", "G", 8, band1},
|
{"RGBA", "G", 8, band1},
|
||||||
{"RGBA", "B", 8, band2},
|
{"RGBA", "B", 8, band2},
|
||||||
{"RGBA", "A", 8, band3},
|
{"RGBA", "A", 8, band3},
|
||||||
|
{"RGBA", "R;16L", 16, band016L},
|
||||||
|
{"RGBA", "G;16L", 16, band116L},
|
||||||
|
{"RGBA", "B;16L", 16, band216L},
|
||||||
|
{"RGBA", "A;16L", 16, band316L},
|
||||||
|
{"RGBA", "R;16B", 16, band016B},
|
||||||
|
{"RGBA", "G;16B", 16, band116B},
|
||||||
|
{"RGBA", "B;16B", 16, band216B},
|
||||||
|
{"RGBA", "A;16B", 16, band316B},
|
||||||
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
{"RGB", "RGB;16N", 48, unpackRGB16B},
|
{"RGB", "RGB;16N", 48, unpackRGB16B},
|
||||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16B},
|
{"RGBA", "RGBa;16N", 64, unpackRGBa16B},
|
||||||
{"RGBA", "RGBA;16N", 64, unpackRGBA16B},
|
{"RGBA", "RGBA;16N", 64, unpackRGBA16B},
|
||||||
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
||||||
|
{"RGB", "R;16N", 16, band016B},
|
||||||
|
{"RGB", "G;16N", 16, band116B},
|
||||||
|
{"RGB", "B;16N", 16, band216B},
|
||||||
|
|
||||||
|
{"RGBA", "R;16N", 16, band016B},
|
||||||
|
{"RGBA", "G;16N", 16, band116B},
|
||||||
|
{"RGBA", "B;16N", 16, band216B},
|
||||||
|
{"RGBA", "A;16N", 16, band316B},
|
||||||
#else
|
#else
|
||||||
{"RGB", "RGB;16N", 48, unpackRGB16L},
|
{"RGB", "RGB;16N", 48, unpackRGB16L},
|
||||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16L},
|
{"RGBA", "RGBa;16N", 64, unpackRGBa16L},
|
||||||
{"RGBA", "RGBA;16N", 64, unpackRGBA16L},
|
{"RGBA", "RGBA;16N", 64, unpackRGBA16L},
|
||||||
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
{"RGBX", "RGBX;16N", 64, unpackRGBA16L},
|
||||||
|
{"RGB", "R;16N", 16, band016L},
|
||||||
|
{"RGB", "G;16N", 16, band116L},
|
||||||
|
{"RGB", "B;16N", 16, band216L},
|
||||||
|
|
||||||
|
|
||||||
|
{"RGBA", "R;16N", 16, band016L},
|
||||||
|
{"RGBA", "G;16N", 16, band116L},
|
||||||
|
{"RGBA", "B;16N", 16, band216L},
|
||||||
|
{"RGBA", "A;16N", 16, band316L},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* true colour w. alpha premultiplied */
|
/* true colour w. alpha premultiplied */
|
||||||
|
|
260
src/map.c
|
@ -28,269 +28,9 @@ PyImaging_CheckBuffer(PyObject *buffer);
|
||||||
extern int
|
extern int
|
||||||
PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
|
PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
|
||||||
/* Standard mapper */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD char *base;
|
|
||||||
int size;
|
|
||||||
int offset;
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE hFile;
|
|
||||||
HANDLE hMap;
|
|
||||||
#endif
|
|
||||||
} ImagingMapperObject;
|
|
||||||
|
|
||||||
static PyTypeObject ImagingMapperType;
|
|
||||||
|
|
||||||
ImagingMapperObject *
|
|
||||||
PyImaging_MapperNew(const char *filename, int readonly) {
|
|
||||||
ImagingMapperObject *mapper;
|
|
||||||
|
|
||||||
if (PyType_Ready(&ImagingMapperType) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType);
|
|
||||||
if (mapper == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper->base = NULL;
|
|
||||||
mapper->size = mapper->offset = 0;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
mapper->hFile = (HANDLE)-1;
|
|
||||||
mapper->hMap = (HANDLE)-1;
|
|
||||||
|
|
||||||
/* FIXME: currently supports readonly mappings only */
|
|
||||||
mapper->hFile = CreateFile(
|
|
||||||
filename,
|
|
||||||
GENERIC_READ,
|
|
||||||
FILE_SHARE_READ,
|
|
||||||
NULL,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
|
||||||
NULL);
|
|
||||||
if (mapper->hFile == (HANDLE)-1) {
|
|
||||||
PyErr_SetString(PyExc_OSError, "cannot open file");
|
|
||||||
Py_DECREF(mapper);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper->hMap = CreateFileMapping(mapper->hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
|
||||||
if (mapper->hMap == (HANDLE)-1) {
|
|
||||||
CloseHandle(mapper->hFile);
|
|
||||||
PyErr_SetString(PyExc_OSError, "cannot map file");
|
|
||||||
Py_DECREF(mapper);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper->base = (char *)MapViewOfFile(mapper->hMap, FILE_MAP_READ, 0, 0, 0);
|
|
||||||
|
|
||||||
mapper->size = GetFileSize(mapper->hFile, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
mapping_dealloc(ImagingMapperObject *mapper) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (mapper->base != 0) {
|
|
||||||
UnmapViewOfFile(mapper->base);
|
|
||||||
}
|
|
||||||
if (mapper->hMap != (HANDLE)-1) {
|
|
||||||
CloseHandle(mapper->hMap);
|
|
||||||
}
|
|
||||||
if (mapper->hFile != (HANDLE)-1) {
|
|
||||||
CloseHandle(mapper->hFile);
|
|
||||||
}
|
|
||||||
mapper->base = 0;
|
|
||||||
mapper->hMap = mapper->hFile = (HANDLE)-1;
|
|
||||||
#endif
|
|
||||||
PyObject_Del(mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
|
||||||
/* standard file operations */
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mapping_read(ImagingMapperObject *mapper, PyObject *args) {
|
|
||||||
PyObject *buf;
|
|
||||||
|
|
||||||
int size = -1;
|
|
||||||
if (!PyArg_ParseTuple(args, "|i", &size)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check size */
|
|
||||||
if (size < 0 || mapper->offset + size > mapper->size) {
|
|
||||||
size = mapper->size - mapper->offset;
|
|
||||||
}
|
|
||||||
if (size < 0) {
|
|
||||||
size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = PyBytes_FromStringAndSize(NULL, size);
|
|
||||||
if (!buf) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 0) {
|
|
||||||
memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size);
|
|
||||||
mapper->offset += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mapping_seek(ImagingMapperObject *mapper, PyObject *args) {
|
|
||||||
int offset;
|
|
||||||
int whence = 0;
|
|
||||||
if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (whence) {
|
|
||||||
case 0: /* SEEK_SET */
|
|
||||||
mapper->offset = offset;
|
|
||||||
break;
|
|
||||||
case 1: /* SEEK_CUR */
|
|
||||||
mapper->offset += offset;
|
|
||||||
break;
|
|
||||||
case 2: /* SEEK_END */
|
|
||||||
mapper->offset = mapper->size + offset;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* FIXME: raise ValueError? */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
|
||||||
/* map entire image */
|
|
||||||
|
|
||||||
extern PyObject *
|
extern PyObject *
|
||||||
PyImagingNew(Imaging im);
|
PyImagingNew(Imaging im);
|
||||||
|
|
||||||
static void
|
|
||||||
ImagingDestroyMap(Imaging im) {
|
|
||||||
return; /* nothing to do! */
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
mapping_readimage(ImagingMapperObject *mapper, PyObject *args) {
|
|
||||||
int y, size;
|
|
||||||
Imaging im;
|
|
||||||
|
|
||||||
char *mode;
|
|
||||||
int xsize;
|
|
||||||
int ysize;
|
|
||||||
int stride;
|
|
||||||
int orientation;
|
|
||||||
if (!PyArg_ParseTuple(
|
|
||||||
args, "s(ii)ii", &mode, &xsize, &ysize, &stride, &orientation)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stride <= 0) {
|
|
||||||
/* FIXME: maybe we should call ImagingNewPrologue instead */
|
|
||||||
if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
|
|
||||||
stride = xsize;
|
|
||||||
} else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) {
|
|
||||||
stride = xsize * 2;
|
|
||||||
} else {
|
|
||||||
stride = xsize * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size = ysize * stride;
|
|
||||||
|
|
||||||
if (mapper->offset + size > mapper->size) {
|
|
||||||
PyErr_SetString(PyExc_OSError, "image file truncated");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
im = ImagingNewPrologue(mode, xsize, ysize);
|
|
||||||
if (!im) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* setup file pointers */
|
|
||||||
if (orientation > 0) {
|
|
||||||
for (y = 0; y < ysize; y++) {
|
|
||||||
im->image[y] = mapper->base + mapper->offset + y * stride;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (y = 0; y < ysize; y++) {
|
|
||||||
im->image[ysize - y - 1] = mapper->base + mapper->offset + y * stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
im->destroy = ImagingDestroyMap;
|
|
||||||
|
|
||||||
mapper->offset += size;
|
|
||||||
|
|
||||||
return PyImagingNew(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct PyMethodDef methods[] = {
|
|
||||||
/* standard file interface */
|
|
||||||
{"read", (PyCFunction)mapping_read, 1},
|
|
||||||
{"seek", (PyCFunction)mapping_seek, 1},
|
|
||||||
/* extensions */
|
|
||||||
{"readimage", (PyCFunction)mapping_readimage, 1},
|
|
||||||
{NULL, NULL} /* sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyTypeObject ImagingMapperType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0) "ImagingMapper", /*tp_name*/
|
|
||||||
sizeof(ImagingMapperObject), /*tp_size*/
|
|
||||||
0, /*tp_itemsize*/
|
|
||||||
/* methods */
|
|
||||||
(destructor)mapping_dealloc, /*tp_dealloc*/
|
|
||||||
0, /*tp_print*/
|
|
||||||
0, /*tp_getattr*/
|
|
||||||
0, /*tp_setattr*/
|
|
||||||
0, /*tp_compare*/
|
|
||||||
0, /*tp_repr*/
|
|
||||||
0, /*tp_as_number */
|
|
||||||
0, /*tp_as_sequence */
|
|
||||||
0, /*tp_as_mapping */
|
|
||||||
0, /*tp_hash*/
|
|
||||||
0, /*tp_call*/
|
|
||||||
0, /*tp_str*/
|
|
||||||
0, /*tp_getattro*/
|
|
||||||
0, /*tp_setattro*/
|
|
||||||
0, /*tp_as_buffer*/
|
|
||||||
Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
|
||||||
0, /*tp_doc*/
|
|
||||||
0, /*tp_traverse*/
|
|
||||||
0, /*tp_clear*/
|
|
||||||
0, /*tp_richcompare*/
|
|
||||||
0, /*tp_weaklistoffset*/
|
|
||||||
0, /*tp_iter*/
|
|
||||||
0, /*tp_iternext*/
|
|
||||||
methods, /*tp_methods*/
|
|
||||||
0, /*tp_members*/
|
|
||||||
0, /*tp_getset*/
|
|
||||||
};
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
PyImaging_Mapper(PyObject *self, PyObject *args) {
|
|
||||||
char *filename;
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &filename)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (PyObject *)PyImaging_MapperNew(filename, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* Buffer mapper */
|
/* Buffer mapper */
|
||||||
|
|
||||||
|
|
101
src/thirdparty/fribidi-shim/fribidi.c
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#else
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define FRIBIDI_SHIM_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include "fribidi.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */
|
||||||
|
FriBidiLevel fribidi_get_par_embedding_levels_ex_compat(
|
||||||
|
const FriBidiCharType *bidi_types,
|
||||||
|
const FriBidiBracketType *bracket_types,
|
||||||
|
const FriBidiStrIndex len,
|
||||||
|
FriBidiParType *pbase_dir,
|
||||||
|
FriBidiLevel *embedding_levels)
|
||||||
|
{
|
||||||
|
return fribidi_get_par_embedding_levels(
|
||||||
|
bidi_types, len, pbase_dir, embedding_levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FriBiDi>=1.0.0 gets bracket types here, ignore */
|
||||||
|
void fribidi_get_bracket_types_compat(
|
||||||
|
const FriBidiChar *str,
|
||||||
|
const FriBidiStrIndex len,
|
||||||
|
const FriBidiCharType *types,
|
||||||
|
FriBidiBracketType *btypes)
|
||||||
|
{ /* no-op*/ }
|
||||||
|
|
||||||
|
|
||||||
|
int load_fribidi(void) {
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
p_fribidi = 0;
|
||||||
|
|
||||||
|
/* Microsoft needs a totally different system */
|
||||||
|
#ifndef _WIN32
|
||||||
|
#define LOAD_FUNCTION(func) \
|
||||||
|
func = (t_##func)dlsym(p_fribidi, #func); \
|
||||||
|
error = error || (func == 0);
|
||||||
|
|
||||||
|
p_fribidi = dlopen("libfribidi.so", RTLD_LAZY);
|
||||||
|
if (!p_fribidi) {
|
||||||
|
p_fribidi = dlopen("libfribidi.so.0", RTLD_LAZY);
|
||||||
|
}
|
||||||
|
if (!p_fribidi) {
|
||||||
|
p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define LOAD_FUNCTION(func) \
|
||||||
|
func = (t_##func)GetProcAddress(p_fribidi, #func); \
|
||||||
|
error = error || (func == 0);
|
||||||
|
|
||||||
|
p_fribidi = LoadLibrary("fribidi");
|
||||||
|
/* MSYS2 */
|
||||||
|
if (!p_fribidi) {
|
||||||
|
p_fribidi = LoadLibrary("libfribidi-0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!p_fribidi) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* load FriBiDi>=1.0.0 functions first, use error to detect version */
|
||||||
|
LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex);
|
||||||
|
LOAD_FUNCTION(fribidi_get_bracket_types);
|
||||||
|
if (error) {
|
||||||
|
/* using FriBiDi<1.0.0, ignore new parameters */
|
||||||
|
error = 0;
|
||||||
|
fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat;
|
||||||
|
fribidi_get_bracket_types = &fribidi_get_bracket_types_compat;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOAD_FUNCTION(fribidi_unicode_to_charset);
|
||||||
|
LOAD_FUNCTION(fribidi_charset_to_unicode);
|
||||||
|
LOAD_FUNCTION(fribidi_get_bidi_types);
|
||||||
|
LOAD_FUNCTION(fribidi_get_par_embedding_levels);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info");
|
||||||
|
if (dlerror() || error || (fribidi_version_info == 0)) {
|
||||||
|
dlclose(p_fribidi);
|
||||||
|
p_fribidi = 0;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
|
||||||
|
if (error || (fribidi_version_info == 0)) {
|
||||||
|
FreeLibrary(p_fribidi);
|
||||||
|
p_fribidi = 0;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
111
src/thirdparty/fribidi-shim/fribidi.h
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
|
||||||
|
#define FRIBIDI_MAJOR_VERSION 1
|
||||||
|
|
||||||
|
/* fribidi-types.h */
|
||||||
|
|
||||||
|
# if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \
|
||||||
|
defined (_sgi) || defined (__sun) || defined (sun) || \
|
||||||
|
defined (__digital__) || defined (__HP_cc)
|
||||||
|
# include <inttypes.h>
|
||||||
|
# elif defined (_AIX)
|
||||||
|
# include <sys/inttypes.h>
|
||||||
|
# else
|
||||||
|
# include <stdint.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
|
typedef uint32_t FriBidiChar;
|
||||||
|
typedef int FriBidiStrIndex;
|
||||||
|
|
||||||
|
typedef FriBidiChar FriBidiBracketType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* fribidi-char-sets.h */
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
_FRIBIDI_CHAR_SET_NOT_FOUND,
|
||||||
|
FRIBIDI_CHAR_SET_UTF8,
|
||||||
|
FRIBIDI_CHAR_SET_CAP_RTL,
|
||||||
|
FRIBIDI_CHAR_SET_ISO8859_6,
|
||||||
|
FRIBIDI_CHAR_SET_ISO8859_8,
|
||||||
|
FRIBIDI_CHAR_SET_CP1255,
|
||||||
|
FRIBIDI_CHAR_SET_CP1256,
|
||||||
|
_FRIBIDI_CHAR_SETS_NUM_PLUS_ONE
|
||||||
|
}
|
||||||
|
FriBidiCharSet;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* fribidi-bidi-types.h */
|
||||||
|
|
||||||
|
typedef signed char FriBidiLevel;
|
||||||
|
|
||||||
|
#define FRIBIDI_TYPE_LTR_VAL 0x00000110L
|
||||||
|
#define FRIBIDI_TYPE_RTL_VAL 0x00000111L
|
||||||
|
#define FRIBIDI_TYPE_ON_VAL 0x00000040L
|
||||||
|
|
||||||
|
typedef uint32_t FriBidiCharType;
|
||||||
|
#define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL
|
||||||
|
|
||||||
|
typedef uint32_t FriBidiParType;
|
||||||
|
#define FRIBIDI_PAR_LTR FRIBIDI_TYPE_LTR_VAL
|
||||||
|
#define FRIBIDI_PAR_RTL FRIBIDI_TYPE_RTL_VAL
|
||||||
|
#define FRIBIDI_PAR_ON FRIBIDI_TYPE_ON_VAL
|
||||||
|
|
||||||
|
#define FRIBIDI_LEVEL_IS_RTL(lev) ((lev) & 1)
|
||||||
|
#define FRIBIDI_DIR_TO_LEVEL(dir) ((FriBidiLevel) (FRIBIDI_IS_RTL(dir) ? 1 : 0))
|
||||||
|
#define FRIBIDI_IS_RTL(p) ((p) & 0x00000001L)
|
||||||
|
#define FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS(p) ((p) & 0x00901000L)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* functions */
|
||||||
|
|
||||||
|
#ifdef FRIBIDI_SHIM_IMPLEMENTATION
|
||||||
|
#define FRIBIDI_ENTRY
|
||||||
|
#else
|
||||||
|
#define FRIBIDI_ENTRY extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define FRIBIDI_FUNC(ret, name, ...) \
|
||||||
|
typedef ret (*t_##name) (__VA_ARGS__); \
|
||||||
|
FRIBIDI_ENTRY t_##name name;
|
||||||
|
|
||||||
|
FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset,
|
||||||
|
FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *);
|
||||||
|
|
||||||
|
FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode,
|
||||||
|
FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *);
|
||||||
|
|
||||||
|
FRIBIDI_FUNC(void, fribidi_get_bidi_types,
|
||||||
|
const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *);
|
||||||
|
|
||||||
|
FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels,
|
||||||
|
const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *,
|
||||||
|
FriBidiLevel *);
|
||||||
|
|
||||||
|
/* FriBiDi>=1.0.0 */
|
||||||
|
FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex,
|
||||||
|
const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex,
|
||||||
|
FriBidiParType *, FriBidiLevel *);
|
||||||
|
|
||||||
|
/* FriBiDi>=1.0.0 */
|
||||||
|
FRIBIDI_FUNC(void, fribidi_get_bracket_types,
|
||||||
|
const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *,
|
||||||
|
FriBidiBracketType *);
|
||||||
|
|
||||||
|
#undef FRIBIDI_FUNC
|
||||||
|
|
||||||
|
/* constant, not a function */
|
||||||
|
FRIBIDI_ENTRY const char *fribidi_version_info;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* shim */
|
||||||
|
|
||||||
|
FRIBIDI_ENTRY void *p_fribidi;
|
||||||
|
|
||||||
|
FRIBIDI_ENTRY int load_fribidi(void);
|
||||||
|
|
||||||
|
#undef FRIBIDI_ENTRY
|
9
src/thirdparty/raqm/AUTHORS
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Abderraouf Adjal
|
||||||
|
Ali Yousuf
|
||||||
|
Anood Almuharbi
|
||||||
|
Asma Albahanta
|
||||||
|
Fahad Alsaidi
|
||||||
|
Ibtisam Almabsali
|
||||||
|
Khaled Hosny
|
||||||
|
Mazoon Almaamari
|
||||||
|
Shamsa Alqassabi
|
22
src/thirdparty/raqm/COPYING
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
|
||||||
|
Copyright © 2016 Khaled Hosny <khaledhosny@eglug.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
89
src/thirdparty/raqm/NEWS
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
Overview of changes leading to 0.7.1
|
||||||
|
Sunday, November 22, 2020
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Require HarfBuzz >= 2.0.0
|
||||||
|
|
||||||
|
Build and documentation fixes.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.7.0
|
||||||
|
Monday, May 27, 2019
|
||||||
|
====================================
|
||||||
|
|
||||||
|
New API:
|
||||||
|
* raqm_version
|
||||||
|
* raqm_version_string
|
||||||
|
* raqm_version_atleast
|
||||||
|
* RAQM_VERSION_MAJOR
|
||||||
|
* RAQM_VERSION_MICRO
|
||||||
|
* RAQM_VERSION_MINOR
|
||||||
|
* RAQM_VERSION_STRING
|
||||||
|
* RAQM_VERSION_ATLEAST
|
||||||
|
|
||||||
|
Overview of changes leading to 0.6.0
|
||||||
|
Sunday, May 5, 2019
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix TTB direction regression from the previous release.
|
||||||
|
|
||||||
|
Correctly detect script of Common and Inherite characters at start of text.
|
||||||
|
|
||||||
|
Undef HAVE_CONFIG_H workaround, for older versions of Fribidi.
|
||||||
|
|
||||||
|
Drop test suite dependency on GLib.
|
||||||
|
|
||||||
|
Port test runner to Python instead of shell script.
|
||||||
|
|
||||||
|
New API:
|
||||||
|
* raqm_set_invisible_glyph()
|
||||||
|
|
||||||
|
Overview of changes leading to 0.5.0
|
||||||
|
Saturday, February 24, 2018
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Use FriBiDi 1.x API when available.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.4.0
|
||||||
|
Sunday, January 21, 2018
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Set begin-of-text and end-of-text HarfBuzz buffer flags.
|
||||||
|
|
||||||
|
Dynamically allocate memory instead of using stack allocation for input text.
|
||||||
|
|
||||||
|
Accept zero length text and do nothing instead of treating it as error.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.3.0
|
||||||
|
Monday, August 21, 2017
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix stack corruption on MSVC.
|
||||||
|
|
||||||
|
New API:
|
||||||
|
* raqm_set_freetype_load_flags
|
||||||
|
|
||||||
|
Overview of changes leading to 0.2.0
|
||||||
|
Wednesday, August 25, 2016
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix building with MSVC due to lacking C99 support.
|
||||||
|
|
||||||
|
Make multiple fonts support actually work. Start and length now respect the
|
||||||
|
input encoding.
|
||||||
|
|
||||||
|
New API:
|
||||||
|
* raqm_index_to_position
|
||||||
|
* raqm_position_to_index
|
||||||
|
* raqm_set_language
|
||||||
|
|
||||||
|
Overview of changes leading to 0.1.1
|
||||||
|
Sunday, May 1, 2016
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix make check on 32-bit systems.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.1.0
|
||||||
|
Wednesday, January 20, 2016
|
||||||
|
====================================
|
||||||
|
|
||||||
|
First release.
|
85
src/thirdparty/raqm/README
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
Raqm
|
||||||
|
====
|
||||||
|
|
||||||
|
[![Linux & macOS build](https://travis-ci.org/HOST-Oman/libraqm.svg?branch=master)](https://travis-ci.org/HOST-Oman/libraqm)
|
||||||
|
[![Windows build](https://img.shields.io/appveyor/ci/HOSTOman/libraqm/master.svg)](https://ci.appveyor.com/project/HOSTOman/libraqm)
|
||||||
|
|
||||||
|
Raqm is a small library that encapsulates the logic for complex text layout and
|
||||||
|
provides a convenient API.
|
||||||
|
|
||||||
|
It currently provides bidirectional text support (using [FriBiDi][1]), shaping
|
||||||
|
(using [HarfBuzz][2]), and proper script itemization. As a result,
|
||||||
|
Raqm can support most writing systems covered by Unicode.
|
||||||
|
|
||||||
|
The documentation can be accessed on the web at:
|
||||||
|
> http://host-oman.github.io/libraqm/
|
||||||
|
|
||||||
|
Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for
|
||||||
|
digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”.
|
||||||
|
|
||||||
|
Building
|
||||||
|
--------
|
||||||
|
|
||||||
|
Raqm depends on the following libraries:
|
||||||
|
* [FreeType][3]
|
||||||
|
* [HarfBuzz][2]
|
||||||
|
* [FriBiDi][1]
|
||||||
|
|
||||||
|
To build the documentation you will also need:
|
||||||
|
* [GTK-Doc][4]
|
||||||
|
|
||||||
|
To install dependencies on Fedora:
|
||||||
|
|
||||||
|
sudo dnf install freetype-devel harfbuzz-devel fribidi-devel gtk-doc
|
||||||
|
|
||||||
|
To install dependencies on Ubuntu:
|
||||||
|
|
||||||
|
sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev \
|
||||||
|
gtk-doc-tools
|
||||||
|
|
||||||
|
On Mac OS X you can use Homebrew:
|
||||||
|
|
||||||
|
brew install freetype harfbuzz fribidi gtk-doc
|
||||||
|
export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs
|
||||||
|
|
||||||
|
Once you have the source code and the dependencies, you can proceed to build.
|
||||||
|
To do that, run the customary sequence of commands in the source code
|
||||||
|
directory:
|
||||||
|
|
||||||
|
$ ./configure
|
||||||
|
$ make
|
||||||
|
$ make install
|
||||||
|
|
||||||
|
To build the documentation, pass `--enable-gtk-doc` to the `configure` script.
|
||||||
|
|
||||||
|
To run the tests:
|
||||||
|
|
||||||
|
$ make check
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Once you have made a change that you are happy with, contribute it back, we’ll
|
||||||
|
be happy to integrate it! Just fork the repository and make a pull request.
|
||||||
|
|
||||||
|
Projects using Raqm
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
1. [ImageMagick](https://github.com/ImageMagick/ImageMagick)
|
||||||
|
2. [LibGD](https://github.com/libgd/libgd)
|
||||||
|
3. [FontView](https://github.com/googlei18n/fontview)
|
||||||
|
4. [Pillow](https://github.com/python-pillow)
|
||||||
|
5. [mplcairo](https://github.com/anntzer/mplcairo)
|
||||||
|
|
||||||
|
The following projects have patches to support complex text layout using Raqm:
|
||||||
|
|
||||||
|
2. SDL_ttf: https://bugzilla.libsdl.org/show_bug.cgi?id=3211
|
||||||
|
3. Pygame: https://bitbucket.org/pygame/pygame/pull-requests/52
|
||||||
|
4. Blender: https://developer.blender.org/D1809
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[1]: http://fribidi.org
|
||||||
|
[2]: http://harfbuzz.org
|
||||||
|
[3]: https://www.freetype.org
|
||||||
|
[4]: https://www.gtk.org/gtk-doc
|
44
src/thirdparty/raqm/raqm-version.h
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _RAQM_H_IN_
|
||||||
|
#error "Include <raqm.h> instead."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _RAQM_VERSION_H_
|
||||||
|
#define _RAQM_VERSION_H_
|
||||||
|
|
||||||
|
#define RAQM_VERSION_MAJOR 0
|
||||||
|
#define RAQM_VERSION_MINOR 7
|
||||||
|
#define RAQM_VERSION_MICRO 1
|
||||||
|
|
||||||
|
#define RAQM_VERSION_STRING "0.7.1"
|
||||||
|
|
||||||
|
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
|
||||||
|
((major)*10000+(minor)*100+(micro) <= \
|
||||||
|
RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO)
|
||||||
|
|
||||||
|
#endif /* _RAQM_VERSION_H_ */
|
2074
src/thirdparty/raqm/raqm.c
vendored
Normal file
|
@ -24,17 +24,14 @@
|
||||||
|
|
||||||
#ifndef _RAQM_H_
|
#ifndef _RAQM_H_
|
||||||
#define _RAQM_H_
|
#define _RAQM_H_
|
||||||
|
#define _RAQM_H_IN_
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef bool
|
#include <stdbool.h>
|
||||||
typedef int bool;
|
#include <stdint.h>
|
||||||
#endif
|
|
||||||
#ifndef uint32_t
|
|
||||||
typedef UINT32 uint32_t;
|
|
||||||
#endif
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
|
@ -42,6 +39,8 @@ typedef UINT32 uint32_t;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "raqm-version.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* raqm_t:
|
* raqm_t:
|
||||||
*
|
*
|
||||||
|
@ -63,7 +62,8 @@ typedef struct _raqm raqm_t;
|
||||||
*
|
*
|
||||||
* Since: 0.1
|
* Since: 0.1
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum
|
||||||
|
{
|
||||||
RAQM_DIRECTION_DEFAULT,
|
RAQM_DIRECTION_DEFAULT,
|
||||||
RAQM_DIRECTION_RTL,
|
RAQM_DIRECTION_RTL,
|
||||||
RAQM_DIRECTION_LTR,
|
RAQM_DIRECTION_LTR,
|
||||||
|
@ -93,64 +93,93 @@ typedef struct raqm_glyph_t {
|
||||||
FT_Face ftface;
|
FT_Face ftface;
|
||||||
} raqm_glyph_t;
|
} raqm_glyph_t;
|
||||||
|
|
||||||
/**
|
raqm_t *
|
||||||
* version 0.1 of the raqm_glyph_t structure
|
raqm_create (void);
|
||||||
*/
|
|
||||||
typedef struct raqm_glyph_t_01 {
|
|
||||||
unsigned int index;
|
|
||||||
int x_advance;
|
|
||||||
int y_advance;
|
|
||||||
int x_offset;
|
|
||||||
int y_offset;
|
|
||||||
uint32_t cluster;
|
|
||||||
} raqm_glyph_t_01;
|
|
||||||
|
|
||||||
raqm_t *
|
raqm_t *
|
||||||
raqm_create(void);
|
raqm_reference (raqm_t *rq);
|
||||||
|
|
||||||
raqm_t *
|
|
||||||
raqm_reference(raqm_t *rq);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
raqm_destroy(raqm_t *rq);
|
raqm_destroy (raqm_t *rq);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_text(raqm_t *rq, const uint32_t *text, size_t len);
|
raqm_set_text (raqm_t *rq,
|
||||||
|
const uint32_t *text,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_text_utf8(raqm_t *rq, const char *text, size_t len);
|
raqm_set_text_utf8 (raqm_t *rq,
|
||||||
|
const char *text,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_par_direction(raqm_t *rq, raqm_direction_t dir);
|
raqm_set_par_direction (raqm_t *rq,
|
||||||
|
raqm_direction_t dir);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_language(raqm_t *rq, const char *lang, size_t start, size_t len);
|
raqm_set_language (raqm_t *rq,
|
||||||
|
const char *lang,
|
||||||
|
size_t start,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_add_font_feature(raqm_t *rq, const char *feature, int len);
|
raqm_add_font_feature (raqm_t *rq,
|
||||||
|
const char *feature,
|
||||||
|
int len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_freetype_face(raqm_t *rq, FT_Face face);
|
raqm_set_freetype_face (raqm_t *rq,
|
||||||
|
FT_Face face);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_freetype_face_range(raqm_t *rq, FT_Face face, size_t start, size_t len);
|
raqm_set_freetype_face_range (raqm_t *rq,
|
||||||
|
FT_Face face,
|
||||||
|
size_t start,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_set_freetype_load_flags(raqm_t *rq, int flags);
|
raqm_set_freetype_load_flags (raqm_t *rq,
|
||||||
|
int flags);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_layout(raqm_t *rq);
|
raqm_set_invisible_glyph (raqm_t *rq,
|
||||||
|
int gid);
|
||||||
|
|
||||||
|
bool
|
||||||
|
raqm_layout (raqm_t *rq);
|
||||||
|
|
||||||
raqm_glyph_t *
|
raqm_glyph_t *
|
||||||
raqm_get_glyphs(raqm_t *rq, size_t *length);
|
raqm_get_glyphs (raqm_t *rq,
|
||||||
|
size_t *length);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_index_to_position(raqm_t *rq, size_t *index, int *x, int *y);
|
raqm_index_to_position (raqm_t *rq,
|
||||||
|
size_t *index,
|
||||||
|
int *x,
|
||||||
|
int *y);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
raqm_position_to_index(raqm_t *rq, int x, int y, size_t *index);
|
raqm_position_to_index (raqm_t *rq,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
size_t *index);
|
||||||
|
|
||||||
|
void
|
||||||
|
raqm_version (unsigned int *major,
|
||||||
|
unsigned int *minor,
|
||||||
|
unsigned int *micro);
|
||||||
|
|
||||||
|
const char *
|
||||||
|
raqm_version_string (void);
|
||||||
|
|
||||||
|
bool
|
||||||
|
raqm_version_atleast (unsigned int major,
|
||||||
|
unsigned int minor,
|
||||||
|
unsigned int micro);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#undef _RAQM_H_IN_
|
||||||
#endif /* _RAQM_H_ */
|
#endif /* _RAQM_H_ */
|
|
@ -275,9 +275,9 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.4.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/2.8.0.zip",
|
||||||
"filename": "harfbuzz-2.7.4.zip",
|
"filename": "harfbuzz-2.8.0.zip",
|
||||||
"dir": "harfbuzz-2.7.4",
|
"dir": "harfbuzz-2.8.0",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||||
cmd_nmake(target="clean"),
|
cmd_nmake(target="clean"),
|
||||||
|
@ -296,21 +296,7 @@ deps = {
|
||||||
cmd_nmake(target="clean"),
|
cmd_nmake(target="clean"),
|
||||||
cmd_nmake(target="fribidi"),
|
cmd_nmake(target="fribidi"),
|
||||||
],
|
],
|
||||||
"headers": [r"lib\*.h"],
|
"bins": [r"*.dll"],
|
||||||
"libs": [r"*.lib"],
|
|
||||||
},
|
|
||||||
"libraqm": {
|
|
||||||
"url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.1.zip",
|
|
||||||
"filename": "libraqm-0.7.1.zip",
|
|
||||||
"dir": "libraqm-0.7.1",
|
|
||||||
"build": [
|
|
||||||
cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"),
|
|
||||||
cmd_cmake(),
|
|
||||||
cmd_nmake(target="clean"),
|
|
||||||
cmd_nmake(target="libraqm"),
|
|
||||||
],
|
|
||||||
"headers": [r"src\*.h"],
|
|
||||||
"bins": [r"libraqm.dll"],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,7 +472,7 @@ def build_pillow():
|
||||||
cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow
|
cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow
|
||||||
cmd_set("MSSdk", "1"), # for PyPy3.6
|
cmd_set("MSSdk", "1"), # for PyPy3.6
|
||||||
cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT
|
cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT
|
||||||
r'"{python_dir}\{python_exe}" setup.py build_ext %*',
|
r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501
|
||||||
]
|
]
|
||||||
|
|
||||||
write_script("build_pillow.cmd", lines)
|
write_script("build_pillow.cmd", lines)
|
||||||
|
@ -511,8 +497,8 @@ if __name__ == "__main__":
|
||||||
verbose = True
|
verbose = True
|
||||||
elif arg == "--no-imagequant":
|
elif arg == "--no-imagequant":
|
||||||
disabled += ["libimagequant"]
|
disabled += ["libimagequant"]
|
||||||
elif arg == "--no-raqm":
|
elif arg == "--no-raqm" or arg == "--no-fribidi":
|
||||||
disabled += ["fribidi", "libraqm"]
|
disabled += ["fribidi"]
|
||||||
elif arg.startswith("--depends="):
|
elif arg.startswith("--depends="):
|
||||||
depends_dir = arg[10:]
|
depends_dir = arg[10:]
|
||||||
elif arg.startswith("--python="):
|
elif arg.startswith("--python="):
|
||||||
|
|
|
@ -93,10 +93,10 @@ fribidi_tab(brackets-type unidata/BidiBrackets.txt)
|
||||||
file(GLOB FRIBIDI_SOURCES lib/*.c)
|
file(GLOB FRIBIDI_SOURCES lib/*.c)
|
||||||
file(GLOB FRIBIDI_HEADERS lib/*.h)
|
file(GLOB FRIBIDI_HEADERS lib/*.h)
|
||||||
|
|
||||||
add_library(fribidi STATIC
|
add_library(fribidi SHARED
|
||||||
${FRIBIDI_SOURCES}
|
${FRIBIDI_SOURCES}
|
||||||
${FRIBIDI_HEADERS}
|
${FRIBIDI_HEADERS}
|
||||||
${FRIBIDI_SOURCES_GENERATED})
|
${FRIBIDI_SOURCES_GENERATED})
|
||||||
fribidi_definitions(fribidi)
|
fribidi_definitions(fribidi)
|
||||||
target_compile_definitions(fribidi
|
target_compile_definitions(fribidi
|
||||||
PUBLIC -DFRIBIDI_LIB_STATIC)
|
PUBLIC "-DFRIBIDI_BUILD")
|
||||||
|
|