Merge branch 'master' into giflzw

This commit is contained in:
Hugo van Kemenade 2021-03-31 16:45:02 +03:00 committed by GitHub
commit 06dfbb8e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 3804 additions and 553 deletions

View File

@ -24,8 +24,8 @@ install:
- mv c:\pillow-depends-master c:\pillow-depends - mv c:\pillow-depends-master c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\ - 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
- ..\pillow-depends\gs9533w32.exe /S - ..\pillow-depends\gs9540w32.exe /S
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.53.3\bin;%PATH% - path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH%
- cd c:\pillow\winbuild\ - cd c:\pillow\winbuild\
- ps: | - ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\

View File

@ -71,8 +71,8 @@ jobs:
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" 7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH
winbuild\depends\gs9533w32.exe /S winbuild\depends\gs9540w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.53.3\bin" >> $env:GITHUB_PATH echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\ xcopy /S /Y winbuild\depends\test_images\* Tests\images\
@ -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'

3
.gitignore vendored
View File

@ -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

View File

@ -5,6 +5,24 @@ Changelog (Pillow)
8.2.0 (unreleased) 8.2.0 (unreleased)
------------------ ------------------
- Add preserve_tone option to autocontrast #5350
[elejke, radarhere]
- 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 - Allow fewer PNG palette entries than the bit depth maximum when saving #5330
[radarhere] [radarhere]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

48
Tests/oss-fuzz/build.sh Executable file
View 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 -@

View 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
View 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()

View File

@ -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
View 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")

View 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

View File

@ -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,
) )
@ -824,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:
@ -843,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:

View File

@ -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
@ -523,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)
@ -549,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)
@ -769,6 +770,20 @@ class TestImage:
reloaded_exif.load(exif.tobytes()) reloaded_exif.load(exif.tobytes())
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) 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],

View File

@ -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

View File

@ -29,6 +29,7 @@ def test_sanity():
ImageOps.autocontrast(hopper("L"), cutoff=(2, 10)) ImageOps.autocontrast(hopper("L"), cutoff=(2, 10))
ImageOps.autocontrast(hopper("L"), ignore=[0, 255]) ImageOps.autocontrast(hopper("L"), ignore=[0, 255])
ImageOps.autocontrast(hopper("L"), mask=hopper("L")) ImageOps.autocontrast(hopper("L"), mask=hopper("L"))
ImageOps.autocontrast(hopper("L"), preserve_tone=True)
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
ImageOps.colorize(hopper("L"), "black", "white") ImageOps.colorize(hopper("L"), "black", "white")
@ -336,7 +337,7 @@ def test_autocontrast_mask_toy_input():
assert ImageStat.Stat(result_nomask).median == [128] assert ImageStat.Stat(result_nomask).median == [128]
def test_auto_contrast_mask_real_input(): def test_autocontrast_mask_real_input():
# Test the autocontrast with a rectangular mask # Test the autocontrast with a rectangular mask
with Image.open("Tests/images/iptc.jpg") as img: with Image.open("Tests/images/iptc.jpg") as img:
@ -362,3 +363,52 @@ def test_auto_contrast_mask_real_input():
threshold=2, threshold=2,
msg="autocontrast without mask pixel incorrect", msg="autocontrast without mask pixel incorrect",
) )
def test_autocontrast_preserve_tone():
def autocontrast(mode, preserve_tone):
im = hopper(mode)
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
assert autocontrast("RGB", True) != autocontrast("RGB", False)
assert autocontrast("L", True) == autocontrast("L", False)
def test_autocontrast_preserve_gradient():
gradient = Image.linear_gradient("L")
# test with a grayscale gradient that extends to 0,255.
# Should be a noop.
out = ImageOps.autocontrast(gradient, cutoff=0, preserve_tone=True)
assert_image_equal(gradient, out)
# cutoff the top and bottom
# autocontrast should make the first and last histogram entries equal
# and, with rounding, should be 10% of the image pixels
out = ImageOps.autocontrast(gradient, cutoff=10, preserve_tone=True)
hist = out.histogram()
assert hist[0] == hist[-1]
assert hist[-1] == 256 * round(256 * 0.10)
# in rgb
img = gradient.convert("RGB")
out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True)
assert_image_equal(img, out)
@pytest.mark.parametrize(
"color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0))
)
def test_autocontrast_preserve_one_color(color):
img = Image.new("RGB", (10, 10), color)
# single color images shouldn't change
out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True)
assert_image_equal(img, out) # single color, no cutoff
# even if there is a cutoff
out = ImageOps.autocontrast(
img, cutoff=10, preserve_tone=True
) # single color 10 cutoff
assert_image_equal(img, out)

View File

@ -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)

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -57,8 +57,9 @@ Windows Installation
We provide Pillow binaries for Windows compiled for the matrix of We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in both 32 and 64-bit versions in the wheel format. supported Pythons in both 32 and 64-bit versions in the wheel format.
These binaries have all of the optional libraries included except These binaries include support for all optional libraries except
for raqm, libimagequant, and libxcb:: libimagequant and libxcb. Raqm support requires
FriBiDi 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
@ -71,8 +72,8 @@ macOS Installation
We provide binaries for macOS for each of the supported Python We provide binaries for macOS for each of the supported Python
versions in the wheel format. These include support for all optional versions in the wheel format. These include support for all optional
libraries except libimagequant and libxcb. Raqm support requires libraries except libimagequant. Raqm support requires
libraqm, fribidi, and harfbuzz to be installed separately:: FriBiDi 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
@ -83,7 +84,7 @@ Linux Installation
We provide binaries for Linux for each of the supported Python We provide binaries for Linux for each of the supported Python
versions in the manylinux wheel format. These include support for all versions in the manylinux wheel format. These include support for all
optional libraries except libimagequant. Raqm support requires optional libraries except libimagequant. Raqm support requires
libraqm, fribidi, and harfbuzz to be installed separately:: FriBiDi 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
@ -191,11 +192,15 @@ Many of Pillow's features require external libraries:
* libraqm depends on the following libraries: FreeType, HarfBuzz, * libraqm depends on the following libraries: FreeType, HarfBuzz,
FriBiDi, make sure that you install them before installing libraqm FriBiDi, make sure that you install them before installing libraqm
if not available as package in your system. if not available as package in your system.
* setting text direction or font features is not supported without * Setting text direction or font features is not supported without libraqm.
libraqm. * Pillow wheels since version 8.2.0 include a modified version of libraqm that
* libraqm is dynamically loaded in Pillow 5.0.0 and above, so support loads libfribidi at runtime if it is installed.
is available if all the libraries are installed. On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
* Windows support: Raqm is not included in prebuilt wheels into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs)
<https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-desktop-applications>`_
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
See `Build Options`_ to see how to build this version.
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
* **libxcb** provides X11 screengrab support. * **libxcb** provides X11 screengrab support.
@ -244,6 +249,12 @@ Build Options
an exception if the libraries are not found. Webpmux (WebP metadata) an exception if the libraries are not found. Webpmux (WebP metadata)
relies on WebP support. Tcl and Tk also must be used together. relies on WebP support. Tcl and Tk also must be used together.
* Build flags: ``--vendor-raqm --vendor-fribidi``
These flags are used to compile a modified version of libraqm and
a shim that dynamically loads libfribidi at runtime. These are
used to compile the standard Pillow wheels. Compiling libraqm requires
a C99-compliant compiler.
* Build flag: ``--disable-platform-guessing``. Skips all of the * Build flag: ``--disable-platform-guessing``. Skips all of the
platform dependent guessing of include and library directories for platform dependent guessing of include and library directories for
automated build systems that configure the proper paths in the automated build systems that configure the proper paths in the

View File

@ -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

120
setup.py
View File

@ -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

View File

@ -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
@ -213,28 +223,7 @@ DECODERS = {}
ENCODERS = {} ENCODERS = {}
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Modes supported by this version # Modes
_MODEINFO = {
# NOTE: this table will be removed in future versions. use
# getmode* functions or ImageMode descriptors instead.
# official modes
"1": ("L", "L", ("1",)),
"L": ("L", "L", ("L",)),
"I": ("L", "I", ("I",)),
"F": ("L", "F", ("F",)),
"P": ("P", "L", ("P",)),
"RGB": ("RGB", "L", ("R", "G", "B")),
"RGBX": ("RGB", "L", ("R", "G", "B", "X")),
"RGBA": ("RGB", "L", ("R", "G", "B", "A")),
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
"LAB": ("RGB", "L", ("L", "A", "B")),
"HSV": ("RGB", "L", ("H", "S", "V")),
# Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
# BGR;24. Use these modes only if you know exactly what you're
# doing...
}
if sys.byteorder == "little": if sys.byteorder == "little":
_ENDIAN = "<" _ENDIAN = "<"
@ -280,7 +269,7 @@ def _conv_type_shape(im):
return (im.size[1], im.size[0], extra), typ return (im.size[1], im.size[0], extra), typ
MODES = sorted(_MODEINFO) MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"]
# raw modes that may be memory mapped. NOTE: if you change this, you # raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too! # may have to modify the stride calculation in map.c too!
@ -535,11 +524,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 +648,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 +1059,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 +1080,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) "
@ -1910,7 +1916,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(self.mode[:-1] + "a") im = self.convert(self.mode[:-1] + "a")
im = im.resize(size, resample, box) im = im.resize(size, resample, box)
return im.convert(self.mode) return im.convert(self.mode)
@ -2394,14 +2400,14 @@ class Image:
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
if self.mode == "LA": if self.mode == "LA" and resample != NEAREST:
return ( return (
self.convert("La") self.convert("La")
.transform(size, method, data, resample, fill, fillcolor) .transform(size, method, data, resample, fill, fillcolor)
.convert("LA") .convert("LA")
) )
if self.mode == "RGBA": if self.mode == "RGBA" and resample != NEAREST:
return ( return (
self.convert("RGBa") self.convert("RGBa")
.transform(size, method, data, resample, fill, fillcolor) .transform(size, method, data, resample, fill, fillcolor)

View File

@ -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()

View File

@ -35,18 +35,28 @@ def getmode(mode):
global _modes global _modes
if not _modes: if not _modes:
# initialize mode cache # initialize mode cache
from . import Image
modes = {} modes = {}
for m, (basemode, basetype, bands) in {
# core modes # core modes
for m, (basemode, basetype, bands) in Image._MODEINFO.items(): "1": ("L", "L", ("1",)),
modes[m] = ModeDescriptor(m, bands, basemode, basetype) "L": ("L", "L", ("L",)),
"I": ("L", "I", ("I",)),
"F": ("L", "F", ("F",)),
"P": ("P", "L", ("P",)),
"RGB": ("RGB", "L", ("R", "G", "B")),
"RGBX": ("RGB", "L", ("R", "G", "B", "X")),
"RGBA": ("RGB", "L", ("R", "G", "B", "A")),
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
"LAB": ("RGB", "L", ("L", "A", "B")),
"HSV": ("RGB", "L", ("H", "S", "V")),
# extra experimental modes # extra experimental modes
modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") "RGBa": ("RGB", "L", ("R", "G", "B", "a")),
modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") "LA": ("L", "L", ("L", "A")),
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") "La": ("L", "L", ("L", "a")),
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") "PA": ("RGB", "L", ("P", "A")),
}.items():
modes[m] = ModeDescriptor(m, bands, basemode, basetype)
# mapping modes # mapping modes
for i16mode in ( for i16mode in (
"I;16", "I;16",

View File

@ -61,7 +61,7 @@ def _lut(image, lut):
# actions # actions
def autocontrast(image, cutoff=0, ignore=None, mask=None): def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False):
""" """
Maximize (normalize) image contrast. This function calculates a Maximize (normalize) image contrast. This function calculates a
histogram of the input image (or mask region), removes ``cutoff`` percent of the histogram of the input image (or mask region), removes ``cutoff`` percent of the
@ -77,9 +77,17 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None):
:param mask: Histogram used in contrast operation is computed using pixels :param mask: Histogram used in contrast operation is computed using pixels
within the mask. If no mask is given the entire image is used within the mask. If no mask is given the entire image is used
for histogram computation. for histogram computation.
:param preserve_tone: Preserve image tone in Photoshop-like style autocontrast.
.. versionadded:: 8.2.0
:return: An image. :return: An image.
""" """
if preserve_tone:
histogram = image.convert("L").histogram(mask)
else:
histogram = image.histogram(mask) histogram = image.histogram(mask)
lut = [] lut = []
for layer in range(0, len(histogram), 256): for layer in range(0, len(histogram), 256):
h = histogram[layer : layer + 256] h = histogram[layer : layer + 256]

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
} }
} }
} }

View File

@ -221,7 +221,7 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t
if (context->next < GIFTABLE) { if (context->next < GIFTABLE) {
/* We'll only add this symbol if we have room /* We'll only add this symbol if we have room
for it (take advise, Netscape!) */ for it (take the advice, Netscape!) */
context->data[context->next] = c; context->data[context->next] = c;
context->link[context->next] = context->lastcode; context->link[context->next] = context->lastcode;

View File

@ -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)) {

View File

@ -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;
@ -385,17 +540,18 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) {
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,130 +664,60 @@ 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);
} }
} }

View File

@ -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 */

104
src/thirdparty/fribidi-shim/fribidi.c vendored Normal file
View File

@ -0,0 +1,104 @@
#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");
if (!p_fribidi) {
p_fribidi = LoadLibrary("fribidi-0");
}
/* 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
View 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
View 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
View 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
View 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
View 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, well
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
View 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

File diff suppressed because it is too large Load Diff

View 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,18 +93,6 @@ typedef struct raqm_glyph_t {
FT_Face ftface; FT_Face ftface;
} raqm_glyph_t; } raqm_glyph_t;
/**
* version 0.1 of the raqm_glyph_t structure
*/
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_create (void);
@ -115,42 +103,83 @@ 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
raqm_set_invisible_glyph (raqm_t *rq,
int gid);
bool bool
raqm_layout (raqm_t *rq); 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_ */

View File

@ -87,7 +87,7 @@ and install Pillow in develop mode (instead of ``python3 -m pip install --editab
Testing Pillow Testing Pillow
-------------- --------------
Some binary dependencies (e.g. ``libraqm.dll``) will be stored in the Some binary dependencies (e.g. ``fribidi.dll``) will be stored in the
``winbuild\build\bin`` directory; this directory should be added to ``PATH`` ``winbuild\build\bin`` directory; this directory should be added to ``PATH``
before running tests. before running tests.

View File

@ -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="):

View File

@ -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")