From 78154765ce54481595c7316a371e3fd04de5a313 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 14:03:10 +0300 Subject: [PATCH 01/16] Update README and remove unnecessary, commented code from helper.py --- Tests/README.txt | 16 +++-- Tests/helper.py | 150 ++--------------------------------------------- 2 files changed, 16 insertions(+), 150 deletions(-) diff --git a/Tests/README.txt b/Tests/README.txt index 169bc4da5..84e2c4655 100644 --- a/Tests/README.txt +++ b/Tests/README.txt @@ -1,13 +1,19 @@ -Minimalistic PIL test framework. +Pillow test files. -Test scripts are named "test_xxx" and are supposed to output "ok". That's it. To run the tests:: - - python setup.py develop +Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. Run the tests from the root of the Pillow source distribution: python selftest.py - python Tests/run.py --installed + nosetests Tests/test_*.py + +Or with coverage: + + coverage run --append --include=PIL/* selftest.py + coverage run --append --include=PIL/* -m nose Tests/test_*.py + coverage report + coverage html + open htmlcov/index.html To run an individual test: diff --git a/Tests/helper.py b/Tests/helper.py index aacbfc009..ebd142bf7 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -160,59 +160,11 @@ class PillowTestCase(unittest.TestCase): return files[0] -# # require that deprecation warnings are triggered -# import warnings -# warnings.simplefilter('default') -# # temporarily turn off resource warnings that warn about unclosed -# # files in the test scripts. -# try: -# warnings.filterwarnings("ignore", category=ResourceWarning) -# except NameError: -# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. -# pass +# helpers import sys py3 = (sys.version_info >= (3, 0)) -# # some test helpers -# -# _target = None -# _tempfiles = [] -# _logfile = None -# -# -# def success(): -# import sys -# success.count += 1 -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return True -# -# -# def failure(msg=None, frame=None): -# import sys -# import linecache -# failure.count += 1 -# if _target: -# if frame is None: -# frame = sys._getframe() -# while frame.f_globals.get("__name__") != _target.__name__: -# frame = frame.f_back -# location = (frame.f_code.co_filename, frame.f_lineno) -# prefix = "%s:%d: " % location -# line = linecache.getline(*location) -# print(prefix + line.strip() + " failed:") -# if msg: -# print("- " + msg) -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return False -# -# success.count = failure.count = 0 -# - - -# helpers def fromstring(data): from io import BytesIO @@ -230,6 +182,9 @@ def tostring(im, format, **options): def lena(mode="RGB", cache={}): from PIL import Image im = None + # FIXME: Implement caching to reduce reading from disk but so an original + # copy is returned each time and the cached image isn't modified by tests + # (for fast, isolated, repeatable tests). # im = cache.get(mode) if im is None: if mode == "RGB": @@ -243,99 +198,4 @@ def lena(mode="RGB", cache={}): # cache[mode] = im return im - -# def assert_image_completely_equal(a, b, msg=None): -# if a != b: -# failure(msg or "images different") -# else: -# success() -# -# -# # test runner -# -# def run(): -# global _target, _tests, run -# import sys -# import traceback -# _target = sys.modules["__main__"] -# run = None # no need to run twice -# tests = [] -# for name, value in list(vars(_target).items()): -# if name[:5] == "test_" and type(value) is type(success): -# tests.append((value.__code__.co_firstlineno, name, value)) -# tests.sort() # sort by line -# for lineno, name, func in tests: -# try: -# _tests = [] -# func() -# for func, args in _tests: -# func(*args) -# except: -# t, v, tb = sys.exc_info() -# tb = tb.tb_next -# if tb: -# failure(frame=tb.tb_frame) -# traceback.print_exception(t, v, tb) -# else: -# print("%s:%d: cannot call test function: %s" % ( -# sys.argv[0], lineno, v)) -# failure.count += 1 -# -# -# def yield_test(function, *args): -# # collect delayed/generated tests -# _tests.append((function, args)) -# -# -# def skip(msg=None): -# import os -# print("skip") -# os._exit(0) # don't run exit handlers -# -# -# def ignore(pattern): -# """Tells the driver to ignore messages matching the pattern, for the -# duration of the current test.""" -# print('ignore: %s' % pattern) -# -# -# def _setup(): -# global _logfile -# -# import sys -# if "--coverage" in sys.argv: -# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) -# with warnings.catch_warnings(): -# warnings.simplefilter("ignore") -# import coverage -# cov = coverage.coverage(auto_data=True, include="PIL/*") -# cov.start() -# -# def report(): -# if run: -# run() -# if success.count and not failure.count: -# print("ok") -# # only clean out tempfiles if test passed -# import os -# import os.path -# import tempfile -# for file in _tempfiles: -# try: -# os.remove(file) -# except OSError: -# pass # report? -# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -# try: -# os.rmdir(temp_root) -# except OSError: -# pass -# -# import atexit -# atexit.register(report) -# -# if "--log" in sys.argv: -# _logfile = open("test.log", "a") -# -# -# _setup() +# End of file From ffce319b540f1e3d94a1d4a10f975641795edf9d Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 14:04:47 +0300 Subject: [PATCH 02/16] Change Tests/README.txt to .md [CI skip] --- Tests/{README.txt => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{README.txt => README.md} (100%) diff --git a/Tests/README.txt b/Tests/README.md similarity index 100% rename from Tests/README.txt rename to Tests/README.md From f41e0a30fbeaabd16642e924415ceede8da8e59c Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:10:20 +0300 Subject: [PATCH 03/16] More tests cleanup --- PIL/tests.py | 17 ----------------- Tests/helper.py | 2 ++ 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 PIL/tests.py diff --git a/PIL/tests.py b/PIL/tests.py deleted file mode 100644 index eb4a8342d..000000000 --- a/PIL/tests.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest - - -class PillowTests(unittest.TestCase): - """ - Can we start moving the test suite here? - """ - - def test_suite_should_move_here(self): - """ - Great idea! - """ - assert True is True - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/helper.py b/Tests/helper.py index ebd142bf7..051912897 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,6 +10,8 @@ else: import unittest +# This should be imported into every test_XXX.py file to report +# any remaining temp files at the end of the run. def tearDownModule(): import glob import os From 1bbe850f5b9b363ea90c358ed0d691d30e334480 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:19:27 +0300 Subject: [PATCH 04/16] Convert bench_cffi_access.py to use unittest and helper.py --- Tests/bench_cffi_access.py | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 8f8ef937a..8aa322aff 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,23 +1,25 @@ -from tester import * +from helper import * -# not running this test by default. No DOS against travis. +# Not running this test by default. No DOS against Travis CI. from PIL import PyAccess -from PIL import Image import time + def iterate_get(size, access): - (w,h) = size + (w, h) = size for x in range(w): for y in range(h): - access[(x,y)] + access[(x, y)] + def iterate_set(size, access): - (w,h) = size + (w, h) = size for x in range(w): for y in range(h): - access[(x,y)] = (x %256,y%256,0) + access[(x, y)] = (x % 256, y % 256, 0) + def timer(func, label, *args): iterations = 5000 @@ -25,27 +27,34 @@ def timer(func, label, *args): for x in range(iterations): func(*args) if time.time()-starttime > 10: - print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0))) + print("%s: breaking at %s iterations, %.6f per iteration" % ( + label, x+1, (time.time()-starttime)/(x+1.0))) break if x == iterations-1: endtime = time.time() - print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0))) + print("%s: %.4f s %.6f per iteration" % ( + label, endtime-starttime, (endtime-starttime)/(x+1.0))) -def test_direct(): - im = lena() - im.load() - #im = Image.new( "RGB", (2000,2000), (1,3,2)) - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - assert_equal(caccess[(0,0)], access[(0,0)]) +class BenchCffiAccess(PillowTestCase): - print ("Size: %sx%s" % im.size) - timer(iterate_get, 'PyAccess - get', im.size, access) - timer(iterate_set, 'PyAccess - set', im.size, access) - timer(iterate_get, 'C-api - get', im.size, caccess) - timer(iterate_set, 'C-api - set', im.size, caccess) - - + def test_direct(self): + im = lena() + im.load() + # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) - + self.assertEqual(caccess[(0, 0)], access[(0, 0)]) + + print ("Size: %sx%s" % im.size) + timer(iterate_get, 'PyAccess - get', im.size, access) + timer(iterate_set, 'PyAccess - set', im.size, access) + timer(iterate_get, 'C-api - get', im.size, caccess) + timer(iterate_set, 'C-api - set', im.size, caccess) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3b3fc441b125bdcf8c57b786fbb61243ea3260c4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:25:51 +0300 Subject: [PATCH 05/16] flake8 Tests/make_hash.py --- Tests/make_hash.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/make_hash.py b/Tests/make_hash.py index 71e208cff..32196e9f8 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -1,7 +1,5 @@ # brute-force search for access descriptor hash table -import random - modes = [ "1", "L", "LA", @@ -13,12 +11,14 @@ modes = [ "YCbCr", ] + def hash(s, i): # djb2 hash: multiply by 33 and xor character for c in s: - i = (((i<<5) + i) ^ ord(c)) & 0xffffffff + i = (((i << 5) + i) ^ ord(c)) & 0xffffffff return i + def check(size, i0): h = [None] * size for m in modes: From c6386a294da5de009b379820bd9416107678c8cb Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:32:52 +0300 Subject: [PATCH 06/16] Convert large_memory_test.py to use unittest (and change second XDIM on line 30 to YDIM) --- Tests/large_memory_test.py | 41 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 148841ec2..57ef67b1c 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -1,4 +1,4 @@ -from tester import * +from helper import * # This test is not run automatically. # @@ -6,22 +6,31 @@ from tester import * # second test. Running this automatically would amount to a denial of # service on our testing infrastructure. I expect this test to fail # on any 32 bit machine, as well as any smallish things (like -# raspberrypis). It does succeed on a 3gb Ubuntu 12.04x64 VM on python -# 2.7 an 3.2 +# Raspberry Pis). It does succeed on a 3gb Ubuntu 12.04x64 VM on Python +# 2.7 an 3.2. from PIL import Image -ydim = 32769 -xdim = 48000 -f = tempfile('temp.png') +YDIM = 32769 +XDIM = 48000 -def _write_png(xdim,ydim): - im = Image.new('L',(xdim,ydim),(0)) - im.save(f) - success() -def test_large(): - """ succeeded prepatch""" - _write_png(xdim,ydim) -def test_2gpx(): - """failed prepatch""" - _write_png(xdim,xdim) +class TestImage(PillowTestCase): + + def _write_png(self, XDIM, YDIM): + f = self.tempfile('temp.png') + im = Image.new('L', (XDIM, YDIM), (0)) + im.save(f) + + def test_large(self): + """ succeeded prepatch""" + self._write_png(XDIM, YDIM) + + def test_2gpx(self): + """failed prepatch""" + self._write_png(XDIM, XDIM) + + +if __name__ == '__main__': + unittest.main() + +# End of file From cf07aa60a18951cf59f373c74823b54a14c2965f Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:43:01 +0300 Subject: [PATCH 07/16] Convert large_memory_numpy_test.py to use unittest (and change second XDIM on line 36 to YDIM) --- Tests/large_memory_numpy_test.py | 45 ++++++++++++++++++-------------- Tests/large_memory_test.py | 4 +-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index eb9b8aa01..17ccd3de2 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -1,4 +1,4 @@ -from tester import * +from helper import * # This test is not run automatically. # @@ -6,32 +6,37 @@ from tester import * # second test. Running this automatically would amount to a denial of # service on our testing infrastructure. I expect this test to fail # on any 32 bit machine, as well as any smallish things (like -# raspberrypis). +# Raspberry Pis). from PIL import Image try: import numpy as np except: - skip() - -ydim = 32769 -xdim = 48000 -f = tempfile('temp.png') + sys.exit("Skipping: Numpy not installed") -def _write_png(xdim,ydim): - dtype = np.uint8 - a = np.zeros((xdim, ydim), dtype=dtype) - im = Image.fromarray(a, 'L') - im.save(f) - success() - -def test_large(): - """ succeeded prepatch""" - _write_png(xdim,ydim) -def test_2gpx(): - """failed prepatch""" - _write_png(xdim,xdim) +YDIM = 32769 +XDIM = 48000 +class LargeMemoryNumpyTest(PillowTestCase): + + def _write_png(self, XDIM, YDIM): + dtype = np.uint8 + a = np.zeros((XDIM, YDIM), dtype=dtype) + f = self.tempfile('temp.png') + im = Image.fromarray(a, 'L') + im.save(f) + + def test_large(self): + """ succeeded prepatch""" + self._write_png(XDIM, YDIM) + + def test_2gpx(self): + """failed prepatch""" + self._write_png(XDIM, YDIM) +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 57ef67b1c..545e284f4 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -14,7 +14,7 @@ YDIM = 32769 XDIM = 48000 -class TestImage(PillowTestCase): +class LargeMemoryTest(PillowTestCase): def _write_png(self, XDIM, YDIM): f = self.tempfile('temp.png') @@ -27,7 +27,7 @@ class TestImage(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, XDIM) + self._write_png(XDIM, YDIM) if __name__ == '__main__': From 5993b97cb04d28114db63cc15cc883ba6580561b Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:46:52 +0300 Subject: [PATCH 08/16] Remove last dependencies on tester.py and remove file --- Tests/bench_get.py | 7 +- Tests/run.py | 135 ---------------- Tests/tester.py | 388 --------------------------------------------- 3 files changed, 4 insertions(+), 526 deletions(-) delete mode 100644 Tests/run.py delete mode 100644 Tests/tester.py diff --git a/Tests/bench_get.py b/Tests/bench_get.py index eca491600..8a1331d39 100644 --- a/Tests/bench_get.py +++ b/Tests/bench_get.py @@ -1,13 +1,14 @@ import sys sys.path.insert(0, ".") -import tester +import helper import timeit + def bench(mode): - im = tester.lena(mode) + im = helper.lena(mode) get = im.im.getpixel - xy = 50, 50 # position shouldn't really matter + xy = 50, 50 # position shouldn't really matter t0 = timeit.default_timer() for i in range(1000000): get(xy) diff --git a/Tests/run.py b/Tests/run.py deleted file mode 100644 index 82e1b94dc..000000000 --- a/Tests/run.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import print_function - -# minimal test runner - -import glob -import os -import os.path -import re -import sys -import tempfile - -try: - root = os.path.dirname(__file__) -except NameError: - root = os.path.dirname(sys.argv[0]) - -if not os.path.isfile("PIL/Image.py"): - print("***", "please run this script from the PIL development directory as") - print("***", "$ python Tests/run.py") - sys.exit(1) - -print("-"*68) - -python_options = [] -tester_options = [] - -if "--installed" not in sys.argv: - os.environ["PYTHONPATH"] = "." - -if "--coverage" in sys.argv: - tester_options.append("--coverage") - -if "--log" in sys.argv: - tester_options.append("--log") - -files = glob.glob(os.path.join(root, "test_*.py")) -files.sort() - -success = failure = 0 -include = [x for x in sys.argv[1:] if x[:2] != "--"] -skipped = [] -failed = [] - -python_options = " ".join(python_options) -tester_options = " ".join(tester_options) - -ignore_re = re.compile('^ignore: (.*)$', re.MULTILINE) - -for file in files: - test, ext = os.path.splitext(os.path.basename(file)) - if include and test not in include: - continue - print("running", test, "...") - # 2>&1 works on unix and on modern windowses. we might care about - # very old Python versions, but not ancient microsoft products :-) - out = os.popen("%s %s -u %s %s 2>&1" % ( - sys.executable, python_options, file, tester_options - )) - result = out.read() - - result_lines = result.splitlines() - if len(result_lines): - if result_lines[0] == "ignore_all_except_last_line": - result = result_lines[-1] - - # Extract any ignore patterns - ignore_pats = ignore_re.findall(result) - result = ignore_re.sub('', result) - - try: - def fix_re(p): - if not p.startswith('^'): - p = '^' + p - if not p.endswith('$'): - p += '$' - return p - - ignore_res = [re.compile(fix_re(p), re.MULTILINE) for p in ignore_pats] - except: - print('(bad ignore patterns %r)' % ignore_pats) - ignore_res = [] - - for r in ignore_res: - result = r.sub('', result) - - result = result.strip() - - if result == "ok": - result = None - elif result == "skip": - print("---", "skipped") # FIXME: driver should include a reason - skipped.append(test) - continue - elif not result: - result = "(no output)" - status = out.close() - if status or result: - if status: - print("=== error", status) - if result: - if result[-3:] == "\nok": - # if there's an ok at the end, it's not really ok - result = result[:-3] - print(result) - failed.append(test) - else: - success += 1 - -print("-"*68) - -temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -tempfiles = glob.glob(os.path.join(temp_root, "temp_*")) -if tempfiles: - print("===", "remaining temporary files") - for file in tempfiles: - print(file) - print("-"*68) - - -def tests(n): - if n == 1: - return "1 test" - else: - return "%d tests" % n - -if skipped: - print("---", tests(len(skipped)), "skipped:") - print(", ".join(skipped)) -if failed: - failure = len(failed) - print("***", tests(failure), "of", (success + failure), "failed:") - print(", ".join(failed)) - sys.exit(1) -else: - print(tests(success), "passed.") diff --git a/Tests/tester.py b/Tests/tester.py deleted file mode 100644 index 866009251..000000000 --- a/Tests/tester.py +++ /dev/null @@ -1,388 +0,0 @@ -from __future__ import print_function - -# require that deprecation warnings are triggered -import warnings -warnings.simplefilter('default') -# temporarily turn off resource warnings that warn about unclosed -# files in the test scripts. -try: - warnings.filterwarnings("ignore", category=ResourceWarning) -except NameError: - # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. - pass - -import sys -py3 = (sys.version_info >= (3, 0)) - -# some test helpers - -_target = None -_tempfiles = [] -_logfile = None - - -def success(): - import sys - success.count += 1 - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return True - - -def failure(msg=None, frame=None): - import sys - import linecache - failure.count += 1 - if _target: - if frame is None: - frame = sys._getframe() - while frame.f_globals.get("__name__") != _target.__name__: - frame = frame.f_back - location = (frame.f_code.co_filename, frame.f_lineno) - prefix = "%s:%d: " % location - line = linecache.getline(*location) - print(prefix + line.strip() + " failed:") - if msg: - print("- " + msg) - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return False - -success.count = failure.count = 0 - - -# predicates - -def assert_true(v, msg=None): - if v: - success() - else: - failure(msg or "got %r, expected true value" % v) - - -def assert_false(v, msg=None): - if v: - failure(msg or "got %r, expected false value" % v) - else: - success() - - -def assert_equal(a, b, msg=None): - if a == b: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_almost_equal(a, b, msg=None, eps=1e-6): - if abs(a-b) < eps: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_deep_equal(a, b, msg=None): - try: - if len(a) == len(b): - if all([x == y for x, y in zip(a, b)]): - success() - else: - failure(msg or "got %s, expected %s" % (a, b)) - else: - failure(msg or "got length %s, expected %s" % (len(a), len(b))) - except: - assert_equal(a, b, msg) - - -def assert_greater(a, b, msg=None): - if a > b: - success() - else: - failure(msg or "%r unexpectedly not greater than %r" % (a, b)) - - -def assert_greater_equal(a, b, msg=None): - if a >= b: - success() - else: - failure( - msg or "%r unexpectedly not greater than or equal to %r" % (a, b)) - - -def assert_less(a, b, msg=None): - if a < b: - success() - else: - failure(msg or "%r unexpectedly not less than %r" % (a, b)) - - -def assert_less_equal(a, b, msg=None): - if a <= b: - success() - else: - failure( - msg or "%r unexpectedly not less than or equal to %r" % (a, b)) - - -def assert_is_instance(a, b, msg=None): - if isinstance(a, b): - success() - else: - failure(msg or "got %r, expected %r" % (type(a), b)) - - -def assert_in(a, b, msg=None): - if a in b: - success() - else: - failure(msg or "%r unexpectedly not in %r" % (a, b)) - - -def assert_match(v, pattern, msg=None): - import re - if re.match(pattern, v): - success() - else: - failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) - - -def assert_exception(exc_class, func): - import sys - import traceback - try: - func() - except exc_class: - success() - except: - failure("expected %r exception, got %r" % ( - exc_class.__name__, sys.exc_info()[0].__name__)) - traceback.print_exc() - else: - failure("expected %r exception, got no exception" % exc_class.__name__) - - -def assert_no_exception(func): - import sys - import traceback - try: - func() - except: - failure("expected no exception, got %r" % sys.exc_info()[0].__name__) - traceback.print_exc() - else: - success() - - -def assert_warning(warn_class, func): - # note: this assert calls func three times! - import warnings - - def warn_error(message, category=UserWarning, **options): - raise category(message) - - def warn_ignore(message, category=UserWarning, **options): - pass - warn = warnings.warn - result = None - try: - warnings.warn = warn_ignore - assert_no_exception(func) - result = func() - warnings.warn = warn_error - assert_exception(warn_class, func) - finally: - warnings.warn = warn # restore - return result - -# helpers - -from io import BytesIO - - -def fromstring(data): - from PIL import Image - return Image.open(BytesIO(data)) - - -def tostring(im, format, **options): - out = BytesIO() - im.save(out, format, **options) - return out.getvalue() - - -def lena(mode="RGB", cache={}): - from PIL import Image - im = cache.get(mode) - if im is None: - if mode == "RGB": - im = Image.open("Tests/images/lena.ppm") - elif mode == "F": - im = lena("L").convert(mode) - elif mode[:4] == "I;16": - im = lena("I").convert(mode) - else: - im = lena("RGB").convert(mode) - cache[mode] = im - return im - - -def assert_image(im, mode, size, msg=None): - if mode is not None and im.mode != mode: - failure(msg or "got mode %r, expected %r" % (im.mode, mode)) - elif size is not None and im.size != size: - failure(msg or "got size %r, expected %r" % (im.size, size)) - else: - success() - - -def assert_image_equal(a, b, msg=None): - if a.mode != b.mode: - failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - failure(msg or "got size %r, expected %r" % (a.size, b.size)) - elif a.tobytes() != b.tobytes(): - failure(msg or "got different content") - else: - success() - - -def assert_image_completely_equal(a, b, msg=None): - if a != b: - failure(msg or "images different") - else: - success() - - -def assert_image_similar(a, b, epsilon, msg=None): - epsilon = float(epsilon) - if a.mode != b.mode: - return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - return failure(msg or "got size %r, expected %r" % (a.size, b.size)) - diff = 0 - try: - ord(b'0') - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(ord(abyte)-ord(bbyte)) - except: - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(abyte-bbyte) - ave_diff = float(diff)/(a.size[0]*a.size[1]) - if epsilon < ave_diff: - return failure( - msg or "average pixel value difference %.4f > epsilon %.4f" % ( - ave_diff, epsilon)) - else: - return success() - - -def tempfile(template, *extra): - import os - import os.path - import sys - import tempfile - files = [] - root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.mkdir(root) - except OSError: - pass - for temp in (template,) + extra: - assert temp[:5] in ("temp.", "temp_") - name = os.path.basename(sys.argv[0]) - name = temp[:4] + os.path.splitext(name)[0][4:] - name = name + "_%d" % len(_tempfiles) + temp[4:] - name = os.path.join(root, name) - files.append(name) - _tempfiles.extend(files) - return files[0] - - -# test runner - -def run(): - global _target, _tests, run - import sys - import traceback - _target = sys.modules["__main__"] - run = None # no need to run twice - tests = [] - for name, value in list(vars(_target).items()): - if name[:5] == "test_" and type(value) is type(success): - tests.append((value.__code__.co_firstlineno, name, value)) - tests.sort() # sort by line - for lineno, name, func in tests: - try: - _tests = [] - func() - for func, args in _tests: - func(*args) - except: - t, v, tb = sys.exc_info() - tb = tb.tb_next - if tb: - failure(frame=tb.tb_frame) - traceback.print_exception(t, v, tb) - else: - print("%s:%d: cannot call test function: %s" % ( - sys.argv[0], lineno, v)) - failure.count += 1 - - -def yield_test(function, *args): - # collect delayed/generated tests - _tests.append((function, args)) - - -def skip(msg=None): - import os - print("skip") - os._exit(0) # don't run exit handlers - - -def ignore(pattern): - """Tells the driver to ignore messages matching the pattern, for the - duration of the current test.""" - print('ignore: %s' % pattern) - - -def _setup(): - global _logfile - - import sys - if "--coverage" in sys.argv: - # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import coverage - cov = coverage.coverage(auto_data=True, include="PIL/*") - cov.start() - - def report(): - if run: - run() - if success.count and not failure.count: - print("ok") - # only clean out tempfiles if test passed - import os - import os.path - import tempfile - for file in _tempfiles: - try: - os.remove(file) - except OSError: - pass # report? - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.rmdir(temp_root) - except OSError: - pass - - import atexit - atexit.register(report) - - if "--log" in sys.argv: - _logfile = open("test.log", "a") - - -_setup() From 433ec1c219fe94d2431e1dbb9a55fc3362b39d64 Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 11:13:33 -0400 Subject: [PATCH 09/16] Clean commit of 16-bit monochrome JPEK2000 support --- PIL/Jpeg2KImagePlugin.py | 24 +++++++++++++----- libImaging/Jpeg2KDecode.c | 51 +++++++++++++++++++++++++++++++++++++++ libImaging/Jpeg2KEncode.c | 33 ++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index c4c980f6e..c75b38576 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,7 +40,10 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - mode = 'L' + if len(yrsiz) > 0 and yrsiz[0] > 8: + mode = 'I' + else: + mode = 'L' elif csiz == 2: mode = 'LA' elif csiz == 3: @@ -78,6 +81,7 @@ def _parse_jp2_header(fp): size = None mode = None + bpc = None hio = io.BytesIO(header) while True: @@ -95,7 +99,9 @@ def _parse_jp2_header(fp): = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: - if nc == 1: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: mode = 'L' elif nc == 2: mode = 'LA' @@ -109,13 +115,19 @@ def _parse_jp2_header(fp): if meth == 1: cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB - if nc == 3: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: + mode = 'L' + elif nc == 3: mode = 'RGB' elif nc == 4: mode = 'RGBA' break elif cs == 17: # grayscale - if nc == 1: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: mode = 'L' elif nc == 2: mode = 'LA' @@ -129,10 +141,10 @@ def _parse_jp2_header(fp): return (size, mode) - ## # Image plugin for JPEG2000 images. + class Jpeg2KImageFile(ImageFile.ImageFile): format = "JPEG2000" format_description = "JPEG 2000 (ISO 15444)" @@ -174,7 +186,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): f.seek(pos, 0) except: length = -1 - + self.tile = [('jpeg2k', (0, 0) + self.size, 0, (self.codec, self.reduce, self.layers, fd, length))] diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 1b61b4f7d..76c1d169b 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -135,6 +135,56 @@ j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } } + +static void +j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) +{ + unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; + unsigned w = tileinfo->x1 - tileinfo->x0; + unsigned h = tileinfo->y1 - tileinfo->y0; + + int shift = 16 - in->comps[0].prec; + int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; + int csiz = (in->comps[0].prec + 7) >> 3; + + unsigned x, y; + + if (csiz == 3) + csiz = 4; + + if (shift < 0) + offset += 1 << (-shift - 1); + + switch (csiz) { + case 1: + for (y = 0; y < h; ++y) { + const UINT8 *data = &tiledata[y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + case 2: + for (y = 0; y < h; ++y) { + const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + case 4: + for (y = 0; y < h; ++y) { + const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + } +} + + static void j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im) @@ -466,6 +516,7 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, static const struct j2k_decode_unpacker j2k_unpackers[] = { { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, + { "I", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index 8e7d0d1f2..8d8737474 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -88,6 +88,22 @@ j2k_pack_l(Imaging im, UINT8 *buf, } } +static void +j2k_pack_i16(Imaging im, UINT8 *buf, + unsigned x0, unsigned y0, unsigned w, unsigned h) +{ + UINT8 *ptr = buf; + unsigned x,y; + for (y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); + for (x = 0; x < w; ++x) { + *ptr++ = *data++; + *ptr++ = *data++; + } + } +} + + static void j2k_pack_la(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) @@ -247,6 +263,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, j2k_pack_tile_t pack; int ret = -1; + unsigned prec = 8; + unsigned bpp = 8; + stream = opj_stream_default_create(OPJ_FALSE); if (!stream) { @@ -271,6 +290,12 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; + } else if (strcmp (im->mode, "I")){ + components = 1; + color_space = OPJ_CLRSPC_GRAY; + pack = j2k_pack_i16; + prec = 16; + bpp = 12; } else if (strcmp (im->mode, "LA") == 0) { components = 2; color_space = OPJ_CLRSPC_GRAY; @@ -298,8 +323,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, image_params[n].w = im->xsize; image_params[n].h = im->ysize; image_params[n].x0 = image_params[n].y0 = 0; - image_params[n].prec = 8; - image_params[n].bpp = 8; + image_params[n].prec = prec; + image_params[n].bpp = bpp; image_params[n].sgnd = 0; } @@ -442,7 +467,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, num_tiles = tiles_x * tiles_y; - state->buffer = malloc (tile_width * tile_height * components); + state->buffer = malloc (tile_width * tile_height * components * prec / 8); tile_ndx = 0; for (y = 0; y < tiles_y; ++y) { @@ -474,7 +499,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, pack(im, state->buffer, pixx, pixy, pixw, pixh); - data_size = pixw * pixh * components; + data_size = pixw * pixh * components * prec / 8; if (!opj_write_tile(codec, tile_ndx++, state->buffer, data_size, stream)) { From 479693417fe2de2ac248fb9f02375e793721d8c8 Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 11:42:06 -0400 Subject: [PATCH 10/16] Merge the rest of the patches Now it actually works and passes the test suite --- PIL/Jpeg2KImagePlugin.py | 8 ++++---- libImaging/Jpeg2KEncode.c | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index c75b38576..ff6ca4b35 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,7 +40,7 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if len(yrsiz) > 0 and yrsiz[0] > 8: + if (ssiz[0] & 0x7f) > 8: mode = 'I' else: mode = 'L' @@ -99,7 +99,7 @@ def _parse_jp2_header(fp): = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' @@ -115,7 +115,7 @@ def _parse_jp2_header(fp): if meth == 1: cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' @@ -125,7 +125,7 @@ def _parse_jp2_header(fp): mode = 'RGBA' break elif cs == 17: # grayscale - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index 8d8737474..e8eef08c1 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -290,7 +290,13 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; - } else if (strcmp (im->mode, "I")){ + } else if (strcmp (im->mode, "I;16") == 0){ + components = 1; + color_space = OPJ_CLRSPC_GRAY; + pack = j2k_pack_i16; + prec = 16; + bpp = 12; + } else if (strcmp (im->mode, "I;16B") == 0){ components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; From d391390e3f14bf897e048ed2388468d42621efdf Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 19:27:43 +0300 Subject: [PATCH 11/16] Second test is meant to have two XDIMs (#728). Also use lowercase for parameters. --- Tests/large_memory_numpy_test.py | 6 +++--- Tests/large_memory_test.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index 17ccd3de2..deb2fc8c6 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -20,9 +20,9 @@ XDIM = 48000 class LargeMemoryNumpyTest(PillowTestCase): - def _write_png(self, XDIM, YDIM): + def _write_png(self, xdim, ydim): dtype = np.uint8 - a = np.zeros((XDIM, YDIM), dtype=dtype) + a = np.zeros((xdim, ydim), dtype=dtype) f = self.tempfile('temp.png') im = Image.fromarray(a, 'L') im.save(f) @@ -33,7 +33,7 @@ class LargeMemoryNumpyTest(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, YDIM) + self._write_png(XDIM, XDIM) if __name__ == '__main__': diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 545e284f4..8552ed4dd 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -16,9 +16,9 @@ XDIM = 48000 class LargeMemoryTest(PillowTestCase): - def _write_png(self, XDIM, YDIM): + def _write_png(self, xdim, ydim): f = self.tempfile('temp.png') - im = Image.new('L', (XDIM, YDIM), (0)) + im = Image.new('L', (xdim, ydim), (0)) im.save(f) def test_large(self): @@ -27,7 +27,7 @@ class LargeMemoryTest(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, YDIM) + self._write_png(XDIM, XDIM) if __name__ == '__main__': From b147dea535cdb05b4692f4d58ec1ca585ad09d8e Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 14:06:56 -0400 Subject: [PATCH 12/16] Add tests and fix a 16bit vs 32bit integer bug Yay unit tests! --- PIL/Jpeg2KImagePlugin.py | 10 ++++----- Tests/images/16bit.cropped.j2k | Bin 0 -> 3886 bytes Tests/images/16bit.cropped.jp2 | Bin 0 -> 3932 bytes Tests/test_file_jpeg2k.py | 36 +++++++++++++++++++++++++++++++++ libImaging/Jpeg2KDecode.c | 9 +++++---- 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 Tests/images/16bit.cropped.j2k create mode 100644 Tests/images/16bit.cropped.jp2 diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index ff6ca4b35..66069802e 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,8 +40,8 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if (ssiz[0] & 0x7f) > 8: - mode = 'I' + if (yrsiz[0] & 0x7f) > 8: + mode = 'I;16' else: mode = 'L' elif csiz == 2: @@ -100,7 +100,7 @@ def _parse_jp2_header(fp): size = (width, height) if unkc: if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 2: @@ -116,7 +116,7 @@ def _parse_jp2_header(fp): cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 3: @@ -126,7 +126,7 @@ def _parse_jp2_header(fp): break elif cs == 17: # grayscale if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 2: diff --git a/Tests/images/16bit.cropped.j2k b/Tests/images/16bit.cropped.j2k new file mode 100644 index 0000000000000000000000000000000000000000..c12df0cd7712f493ff5e49b16ee3cabd4fbea889 GIT binary patch literal 3886 zcmV+}57F@dPybN>DF6Tf002M$002M$0000000000002M$002M$00000000000S^HI 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 literal 0 HcmV?d00001 diff --git a/Tests/images/16bit.cropped.jp2 b/Tests/images/16bit.cropped.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..0f6685a462f5f1f5abfaf2ce4d141baba4c0da54 GIT binary patch literal 3932 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b662124b4..b763687f7 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -120,6 +120,42 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(j2k.mode, 'RGBA') self.assertEqual(jp2.mode, 'RGBA') + def test_16bit_monochrome_has_correct_mode(self): + + j2k = Image.open('Tests/images/16bit.cropped.j2k') + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + + j2k.load() + jp2.load() + + self.assertEqual(j2k.mode, 'I;16') + self.assertEqual(jp2.mode, 'I;16') + + def test_16bit_monchrome_jp2_like_tiff(self): + + tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + self.assert_image_similar(jp2, tiff_16bit, 1e-3) + + def test_16bit_monchrome_j2k_like_tiff(self): + + tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') + j2k = Image.open('Tests/images/16bit.cropped.j2k') + self.assert_image_similar(j2k, tiff_16bit, 1e-3) + + def test_16bit_j2k_roundtrips(self): + + j2k = Image.open('Tests/images/16bit.cropped.j2k') + im = self.roundtrip(j2k) + self.assert_image_equal(im, j2k) + + def test_16bit_jp2_roundtrips(self): + + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + im = self.roundtrip(jp2) + self.assert_image_equal(im, jp2) + + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 76c1d169b..97ec81003 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -160,7 +160,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 1: for (y = 0; y < h; ++y) { const UINT8 *data = &tiledata[y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -168,7 +168,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 2: for (y = 0; y < h; ++y) { const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -176,7 +176,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 4: for (y = 0; y < h; ++y) { const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -516,7 +516,8 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, static const struct j2k_decode_unpacker j2k_unpackers[] = { { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, - { "I", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, + { "I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, + { "I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, From 87aaa2ecd734d2edc6cfea7e51169c68d5d2233f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 25 Jun 2014 22:28:27 -0700 Subject: [PATCH 13/16] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 06f6e7090..60841e663 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- 16-bit monochrome support for JPEG2000 + [videan42] + - Fixed ImagePalette.save [brightpisces] From ea4bccf54416cc14b140ef5758044650764d031f Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 26 Jun 2014 08:34:04 -0400 Subject: [PATCH 14/16] No longer use Tests/run.py --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index dca36d4ed..a226ea602 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ pre: bin/python setup.py develop bin/python selftest.py - bin/python Tests/run.py check-manifest pyroma . viewdoc From 85d8d4bb8f664a64eb00fde379fdece3a5d85f1d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 26 Jun 2014 08:35:46 -0400 Subject: [PATCH 15/16] Fix manifest --- MANIFEST.in | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index c2358f76f..79a70dddd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include *.c include *.h include *.py include *.rst +include .coveragerc include .gitattributes include .travis.yml include Makefile @@ -28,28 +29,42 @@ recursive-include Sane CHANGES recursive-include Sane README recursive-include Scripts *.py recursive-include Scripts README +recursive-include Tests *.bdf recursive-include Tests *.bin recursive-include Tests *.bmp +recursive-include Tests *.doc recursive-include Tests *.eps +recursive-include Tests *.fli recursive-include Tests *.gif recursive-include Tests *.gnuplot recursive-include Tests *.html recursive-include Tests *.icm recursive-include Tests *.icns recursive-include Tests *.ico +recursive-include Tests *.j2k recursive-include Tests *.jp2 recursive-include Tests *.jpg +recursive-include Tests *.lut +recursive-include Tests *.pbm recursive-include Tests *.pcf recursive-include Tests *.pcx +recursive-include Tests *.pgm +recursive-include Tests *.pil recursive-include Tests *.png recursive-include Tests *.ppm +recursive-include Tests *.psd recursive-include Tests *.py +recursive-include Tests *.spider +recursive-include Tests *.tar recursive-include Tests *.tif recursive-include Tests *.tiff recursive-include Tests *.ttf recursive-include Tests *.txt +recursive-include Tests *.webp +recursive-include Tests *.xpm recursive-include Tk *.c recursive-include Tk *.txt +recursive-include Tk *.rst recursive-include depends *.sh recursive-include docs *.bat recursive-include docs *.gitignore @@ -64,3 +79,5 @@ recursive-include docs COPYING recursive-include docs LICENSE recursive-include libImaging *.c recursive-include libImaging *.h +recursive-include Sane *.rst +recursive-include Scripts *.rst From f6171f8bbe33fa0f9be3fad0e1520c5ead5d5d6d Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 26 Jun 2014 20:59:51 +0300 Subject: [PATCH 16/16] Update CHANGES.rst [CI skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 60841e663..3f4299f5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,18 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Fix test_imagedraw failures #727 + [cgohlke] + +- Fix AttributeError: class Image has no attribute 'DEBUG' #726 + [cgohlke] + +- Fix msvc warning: 'inline' : macro redefinition #725 + [cgohlke] + +- Cleanup #654 + [dvska, hugovk, wiredfool] + - 16-bit monochrome support for JPEG2000 [videan42]