├── README ├── tests ├── test.jpg ├── 1024.jpg ├── 64.jpg ├── benchmark.py └── test_api.py ├── MANIFEST.in ├── .gitignore ├── LICENSE ├── README.md ├── src └── jpeg4py │ ├── __init__.py │ ├── _cffi.py │ └── _py.py └── setup.py /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /tests/test.jpg: -------------------------------------------------------------------------------- 1 | 64.jpg -------------------------------------------------------------------------------- /tests/1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajkxyz/jpeg4py/HEAD/tests/1024.jpg -------------------------------------------------------------------------------- /tests/64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajkxyz/jpeg4py/HEAD/tests/64.jpg -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/jpeg4py/*.py 2 | include tests/*.py 3 | include tests/*.jpg 4 | include LICENSE 5 | include README 6 | include README.md 7 | include setup.py 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jpeg4py 2 | ======= 3 | 4 | Python cffi libjpeg-turbo bindings and helper classes. 5 | 6 | The purpose of this package is to provide thread-safe and aware of GIL Python 7 | bindings to libjpeg-turbo which work with numpy arrays on 8 | Python 2, 3 and PyPy. 9 | 10 | Tested with Python 2.7, Python 3.4 and PyPy on Ubuntu 14.04. 11 | 12 | Covered TurboJPEG API: 13 | ``` 14 | tjInitDecompress 15 | tjDecompressHeader2 16 | tjDecompress2 17 | ``` 18 | so, currently, only decoding of jpeg files is possible, and 19 | it is about 1.3 times faster than Image.open().tobytes() and 20 | scipy.misc.imread() in a single thread and up to 9 times faster in 21 | multithreaded mode. 22 | 23 | Installation 24 | ------------ 25 | 26 | Requirements: 27 | 28 | 1. numpy 29 | 2. libjpeg-turbo 30 | 31 | On Ubuntu, the shared library is included in libturbojpeg package: 32 | ```bash 33 | sudo apt-get install libturbojpeg 34 | ``` 35 | 36 | On Windows, you can download installer from the official libjpeg-turbo [Sourceforge](https://sourceforge.net/projects/libjpeg-turbo/files) 37 | repository, install it and copy turbojpeg.dll to the directory from the system PATH. 38 | 39 | On Mac OS X, you can download the DMG from the official libjpeg-turbo [Sourceforge](https://sourceforge.net/projects/libjpeg-turbo/files) 40 | repository and install it. 41 | 42 | If you have a custom library which is TurboJPEG API compatible, 43 | just call jpeg4py.initialize with tuple containing that library's file name. 44 | 45 | To install the module run: 46 | ```bash 47 | python -m pip install jpeg4py 48 | ``` 49 | or 50 | ```bash 51 | python setup.py install 52 | ``` 53 | or just copy src/jpeg4py to any place where python interpreter will be able 54 | to find it. 55 | 56 | Tests 57 | ----- 58 | 59 | To run the tests, execute: 60 | ```bash 61 | PYTHONPATH=src python -m nose -w tests 62 | ``` 63 | 64 | Example usage: 65 | -------------- 66 | 67 | ```python 68 | import jpeg4py as jpeg 69 | import matplotlib.pyplot as pp 70 | 71 | 72 | if __name__ == "__main__": 73 | pp.imshow(jpeg.JPEG("test.jpg").decode()) 74 | pp.show() 75 | ``` 76 | 77 | License 78 | ------- 79 | 80 | Released under Simplified BSD License. 81 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 82 | -------------------------------------------------------------------------------- /src/jpeg4py/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | Init module. 38 | """ 39 | 40 | # High-level interface 41 | from jpeg4py._py import JPEG, JPEGRuntimeError 42 | 43 | # Low-level interface 44 | from jpeg4py._cffi import ffi, lib, initialize 45 | 46 | # Constants 47 | from jpeg4py._cffi import (TJSAMP_444, 48 | TJSAMP_422, 49 | TJSAMP_420, 50 | TJSAMP_GRAY, 51 | TJSAMP_440, 52 | TJPF_RGB, 53 | TJPF_BGR, 54 | TJPF_RGBX, 55 | TJPF_BGRX, 56 | TJPF_XBGR, 57 | TJPF_XRGB, 58 | TJPF_GRAY, 59 | TJPF_RGBA, 60 | TJPF_BGRA, 61 | TJPF_ABGR, 62 | TJPF_ARGB) 63 | 64 | # Mappings 65 | from jpeg4py._cffi import tjPixelSize 66 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | Setup script. 38 | """ 39 | try: 40 | from setuptools import setup 41 | except ImportError: 42 | from distutils.core import setup 43 | 44 | 45 | setup( 46 | name="jpeg4py", 47 | description="libjpeg-turbo cffi bindings and helper classes", 48 | version="0.1.4", 49 | license="Simplified BSD", 50 | author="Samsung Electronics Co.,Ltd.", 51 | author_email="ajk.xyz@gmail.com", 52 | url="https://github.com/ajkxyz/jpeg4py", 53 | download_url='https://github.com/ajkxyz/jpeg4py', 54 | packages=["jpeg4py"], 55 | package_dir={"jpeg4py": "src/jpeg4py"}, 56 | install_requires=["cffi", "numpy"], 57 | keywords=["libjpeg-turbo", "jpeg4py"], 58 | classifiers=[ 59 | "Development Status :: 4 - Beta", 60 | "Environment :: Console", 61 | "Intended Audience :: Developers", 62 | "License :: OSI Approved :: BSD License", 63 | "Operating System :: POSIX", 64 | "Programming Language :: Python :: 2.7", 65 | "Programming Language :: Python :: 3.2", 66 | "Programming Language :: Python :: 3.3", 67 | "Programming Language :: Python :: 3.4", 68 | "Programming Language :: Python :: 3.5", 69 | "Programming Language :: Python :: 3.6", 70 | "Topic :: Software Development :: Libraries" 71 | ] 72 | ) 73 | -------------------------------------------------------------------------------- /tests/benchmark.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | jpeg4py.JPEG().decode() vs scipy.misc.imread() vs Image.open().tobytes() 38 | """ 39 | import unittest 40 | import logging 41 | import jpeg4py as jpeg 42 | import os 43 | import time 44 | import threading 45 | import scipy.misc 46 | try: 47 | import Image 48 | no_pil = False 49 | except ImportError: 50 | no_pil = True 51 | 52 | 53 | class Test(unittest.TestCase): 54 | def setUp(self): 55 | logging.basicConfig(level=logging.DEBUG) 56 | self.dirnme = os.path.dirname(__file__) 57 | self.fnme = (os.path.join(self.dirnme, "1024.jpg") if len(self.dirnme) 58 | else "1024.jpg") 59 | 60 | def tearDown(self): 61 | pass 62 | 63 | def looped(self, target, n): 64 | for i in range(n): 65 | target() 66 | 67 | def bench(self, target, n_threads, n): 68 | t0 = time.time() 69 | th = [] 70 | for i in range(n_threads): 71 | th.append(threading.Thread(target=self.looped, args=(target, n))) 72 | th[-1].start() 73 | for t in th: 74 | t.join() 75 | return time.time() - t0 76 | 77 | def bench_jpeg4py(self): 78 | a = jpeg.JPEG(self.fnme).decode() 79 | del a 80 | 81 | def bench_scipy(self): 82 | a = scipy.misc.imread(self.fnme) 83 | del a 84 | 85 | def bench_pil(self): 86 | a = Image.open(self.fnme).tobytes() 87 | del a 88 | 89 | def do_bench(self, n_threads, n): 90 | dt_jpeg4py = self.bench(self.bench_jpeg4py, n_threads, n) 91 | logging.info("jpeg4py decoded %d images per thread and %d threads " 92 | "in %.3f sec", n, n_threads, dt_jpeg4py) 93 | dt_scipy = self.bench(self.bench_scipy, n_threads, n) 94 | logging.info("scipy decoded %d images per thread and %d threads " 95 | "in %.3f sec", n, n_threads, dt_scipy) 96 | global no_pil 97 | if not no_pil: 98 | dt_pil = self.bench(self.bench_pil, n_threads, n) 99 | logging.info("pil decoded %d images per thread and %d threads " 100 | "in %.3f sec", n, n_threads, dt_pil) 101 | logging.info("With %d images per thread and %d threads jpeg4py vs " 102 | "(scipy, pil) = (%.1f, %.1f) times", n, n_threads, 103 | dt_scipy / dt_jpeg4py, dt_pil / dt_jpeg4py) 104 | else: 105 | logging.info("With %d images per thread and %d threads jpeg4py vs " 106 | "scipy = %.1f times", n, n_threads, 107 | dt_scipy / dt_jpeg4py) 108 | 109 | def test_1_thread(self): 110 | self.do_bench(1, 50) 111 | 112 | def test_2_threads(self): 113 | self.do_bench(2, 50) 114 | 115 | def test_8_threads(self): 116 | self.do_bench(8, 50) 117 | 118 | def test_32_threads(self): 119 | self.do_bench(32, 50) 120 | 121 | def test_768_threads(self): 122 | logging.info("Will test 768 threads on small file") 123 | self.fnme = (os.path.join(self.dirnme, "64.jpg") if len(self.dirnme) 124 | else "64.jpg") 125 | self.do_bench(768, 50) 126 | 127 | 128 | if __name__ == "__main__": 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | Tests some of the api in jpeg4py package. 38 | """ 39 | import unittest 40 | import logging 41 | import jpeg4py as jpeg 42 | import numpy 43 | import os 44 | import gc 45 | 46 | 47 | class Test(unittest.TestCase): 48 | def setUp(self): 49 | logging.basicConfig(level=logging.DEBUG) 50 | dirnme = os.path.dirname(__file__) 51 | fnme = os.path.join(dirnme, "test.jpg") if len(dirnme) else "test.jpg" 52 | fin = open(fnme, "rb") 53 | self.raw = numpy.empty(os.path.getsize(fnme), dtype=numpy.uint8) 54 | fin.readinto(self.raw) 55 | fin.close() 56 | 57 | def tearDown(self): 58 | pass 59 | 60 | def test_initialize(self): 61 | jpeg.initialize() 62 | lib = jpeg.lib 63 | jpeg.initialize() 64 | self.assertEqual(jpeg.lib, lib) 65 | 66 | def test_constants(self): 67 | self.assertEqual(jpeg.TJSAMP_444, 0) 68 | self.assertEqual(jpeg.TJSAMP_422, 1) 69 | self.assertEqual(jpeg.TJSAMP_420, 2) 70 | self.assertEqual(jpeg.TJSAMP_GRAY, 3) 71 | self.assertEqual(jpeg.TJSAMP_440, 4) 72 | self.assertEqual(jpeg.TJPF_RGB, 0) 73 | self.assertEqual(jpeg.TJPF_BGR, 1) 74 | self.assertEqual(jpeg.TJPF_RGBX, 2) 75 | self.assertEqual(jpeg.TJPF_BGRX, 3) 76 | self.assertEqual(jpeg.TJPF_XBGR, 4) 77 | self.assertEqual(jpeg.TJPF_XRGB, 5) 78 | self.assertEqual(jpeg.TJPF_GRAY, 6) 79 | self.assertEqual(jpeg.TJPF_RGBA, 7) 80 | self.assertEqual(jpeg.TJPF_BGRA, 8) 81 | self.assertEqual(jpeg.TJPF_ABGR, 9) 82 | self.assertEqual(jpeg.TJPF_ARGB, 10) 83 | 84 | def test_parse_header(self): 85 | raw = self.raw.copy() 86 | jp = jpeg.JPEG(raw) 87 | jp.parse_header() 88 | self.assertIsNotNone(jp.width) 89 | self.assertIsNotNone(jp.height) 90 | self.assertIsNotNone(jp.subsampling) 91 | return jp 92 | 93 | def test_clear(self): 94 | gc.collect() 95 | jp = self.test_parse_header() 96 | jp2 = self.test_parse_header() 97 | del jp 98 | gc.collect() 99 | self.assertEqual(len(jpeg.JPEG.decompressors), 1) 100 | del jp2 101 | gc.collect() 102 | self.assertEqual(len(jpeg.JPEG.decompressors), 2) 103 | jp = self.test_parse_header() 104 | self.assertEqual(len(jpeg.JPEG.decompressors), 1) 105 | del jp 106 | gc.collect() 107 | jpeg.JPEG.clear() 108 | self.assertEqual(len(jpeg.JPEG.decompressors), 0) 109 | jp = self.test_parse_header() 110 | self.assertEqual(len(jpeg.JPEG.decompressors), 0) 111 | del jp 112 | gc.collect() 113 | 114 | def test_decode(self): 115 | jp = self.test_parse_header() 116 | a = jp.decode() 117 | self.assertEqual(len(a.shape), 3) 118 | self.assertEqual(a.shape[0], jp.height) 119 | self.assertEqual(a.shape[1], jp.width) 120 | self.assertEqual(a.shape[2], 3) 121 | a = jp.decode(pixfmt=jpeg.TJPF_GRAY) 122 | self.assertEqual(len(a.shape), 2) 123 | self.assertEqual(a.shape[0], jp.height) 124 | self.assertEqual(a.shape[1], jp.width) 125 | #import matplotlib.pyplot as pp 126 | #pp.axis("off") 127 | #pp.imshow(a) 128 | #pp.show() 129 | 130 | 131 | if __name__ == "__main__": 132 | unittest.main() 133 | -------------------------------------------------------------------------------- /src/jpeg4py/_cffi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | libjpeg-turbo cffi bindings. 38 | """ 39 | import cffi 40 | import threading 41 | 42 | 43 | #: Subsumplings 44 | TJSAMP_444 = 0 45 | TJSAMP_422 = 1 46 | TJSAMP_420 = 2 47 | TJSAMP_GRAY = 3 48 | TJSAMP_440 = 4 49 | 50 | #: Pixel formats 51 | TJPF_RGB = 0 52 | TJPF_BGR = 1 53 | TJPF_RGBX = 2 54 | TJPF_BGRX = 3 55 | TJPF_XBGR = 4 56 | TJPF_XRGB = 5 57 | TJPF_GRAY = 6 58 | TJPF_RGBA = 7 59 | TJPF_BGRA = 8 60 | TJPF_ABGR = 9 61 | TJPF_ARGB = 10 62 | 63 | 64 | #: Pixel format to Bytes per pixel mapping 65 | tjPixelSize = {TJPF_RGB: 3, TJPF_BGR: 3, TJPF_RGBX: 4, TJPF_BGRX: 4, 66 | TJPF_XBGR: 4, TJPF_XRGB: 4, TJPF_GRAY: 1, TJPF_RGBA: 4, 67 | TJPF_BGRA: 4, TJPF_ABGR: 4, TJPF_ARGB: 4} 68 | 69 | 70 | #: ffi parser 71 | ffi = None 72 | 73 | 74 | #: Loaded shared library 75 | lib = None 76 | 77 | 78 | #: Lock 79 | lock = threading.Lock() 80 | 81 | 82 | def _initialize(backends): 83 | global lib 84 | if lib is not None: 85 | return 86 | # C function definitions 87 | src = """ 88 | typedef void *tjhandle; 89 | 90 | typedef struct { 91 | int x; 92 | int y; 93 | int w; 94 | int h; 95 | } tjregion; 96 | 97 | typedef struct { 98 | tjregion r; 99 | int op; 100 | int options; 101 | void *data; 102 | void *customFilter; 103 | } tjtransform; 104 | 105 | typedef struct { 106 | int num; 107 | int denom; 108 | } tjscalingfactor; 109 | 110 | tjhandle tjInitDecompress(); 111 | int tjDestroy(tjhandle handle); 112 | int tjDecompressToYUV( 113 | tjhandle handle, 114 | unsigned char *jpegBuf, 115 | unsigned long jpegSize, 116 | unsigned char *dstBuf, 117 | int flags); 118 | unsigned long tjBufSizeYUV(int width, int height, int subsamp); 119 | int tjDecompressHeader2( 120 | tjhandle handle, 121 | unsigned char *jpegBuf, 122 | unsigned long jpegSize, 123 | int *width, 124 | int *height, 125 | int *jpegSubsamp); 126 | int tjDecompress2( 127 | tjhandle handle, 128 | unsigned char *jpegBuf, 129 | unsigned long jpegSize, 130 | unsigned char *dstBuf, 131 | int width, 132 | int pitch, 133 | int height, 134 | int pixelFormat, 135 | int flags); 136 | tjhandle tjInitCompress(); 137 | int tjCompress2( 138 | tjhandle handle, 139 | unsigned char *srcBuf, 140 | int width, 141 | int pitch, 142 | int height, 143 | int pixelFormat, 144 | unsigned char **jpegBuf, 145 | unsigned long *jpegSize, 146 | int jpegSubsamp, 147 | int jpegQual, 148 | int flags); 149 | unsigned long tjBufSize( 150 | int width, 151 | int height, 152 | int jpegSubsamp); 153 | int tjEncodeYUV2( 154 | tjhandle handle, 155 | unsigned char *srcBuf, 156 | int width, 157 | int pitch, 158 | int height, 159 | int pixelFormat, 160 | unsigned char *dstBuf, 161 | int subsamp, 162 | int flags); 163 | char* tjGetErrorStr(); 164 | tjhandle tjInitTransform(); 165 | int tjTransform( 166 | tjhandle handle, 167 | unsigned char *jpegBuf, 168 | unsigned long jpegSize, 169 | int n, 170 | unsigned char **dstBufs, 171 | unsigned long *dstSizes, 172 | tjtransform *transforms, 173 | int flags); 174 | unsigned char *tjAlloc(int bytes); 175 | void tjFree(unsigned char *buffer); 176 | tjscalingfactor *tjGetScalingFactors(int *numscalingfactors); 177 | """ 178 | 179 | # Parse 180 | global ffi 181 | ffi = cffi.FFI() 182 | ffi.cdef(src) 183 | 184 | # Load library 185 | for libnme in backends: 186 | try: 187 | lib = ffi.dlopen(libnme) 188 | break 189 | except OSError: 190 | pass 191 | else: 192 | ffi = None 193 | raise OSError("Could not load libjpeg-turbo library") 194 | 195 | 196 | def initialize( 197 | backends=( 198 | "libturbojpeg.so.0", # for Ubuntu 199 | "turbojpeg.dll", # for Windows 200 | "/opt/libjpeg-turbo/lib64/libturbojpeg.0.dylib", # for Mac OS X 201 | )): 202 | """Loads the shared library if it was not loaded yet. 203 | 204 | Parameters: 205 | backends: tuple of shared library file names to try to load. 206 | """ 207 | global lib 208 | if lib is not None: 209 | return 210 | global lock 211 | with lock: 212 | _initialize(backends) 213 | -------------------------------------------------------------------------------- /src/jpeg4py/_py.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2014, Samsung Electronics Co.,Ltd. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of Samsung Electronics Co.,Ltd.. 28 | """ 29 | 30 | """ 31 | jpeg4py - libjpeg-turbo cffi bindings and helper classes. 32 | URL: https://github.com/ajkxyz/jpeg4py 33 | Original author: Alexey Kazantsev 34 | """ 35 | 36 | """ 37 | Helper classes for libjpeg-turbo cffi bindings. 38 | """ 39 | import jpeg4py._cffi as jpeg 40 | from jpeg4py._cffi import TJPF_RGB 41 | import numpy 42 | import os 43 | 44 | 45 | class JPEGRuntimeError(RuntimeError): 46 | def __init__(self, msg, code): 47 | super(JPEGRuntimeError, self).__init__(msg) 48 | self.code = code 49 | 50 | 51 | class Base(object): 52 | """Base class. 53 | 54 | Attributes: 55 | lib_: cffi handle to loaded shared library. 56 | """ 57 | def __init__(self, lib_): 58 | """Constructor. 59 | 60 | Parameters: 61 | lib_: cffi handle to loaded shared library. 62 | """ 63 | if lib_ is None: 64 | jpeg.initialize() 65 | lib_ = jpeg.lib 66 | self.lib_ = lib_ 67 | 68 | def get_last_error(self): 69 | """Returns last error string. 70 | """ 71 | return jpeg.ffi.string(self.lib_.tjGetErrorStr()).decode("utf-8") 72 | 73 | 74 | class Handle(Base): 75 | """Stores tjhandle pointer. 76 | 77 | Attributes: 78 | handle_: cffi tjhandle pointer. 79 | """ 80 | def __init__(self, handle_, lib_): 81 | """Constructor. 82 | 83 | Parameters: 84 | handle_: cffi tjhandle pointer. 85 | """ 86 | self.handle_ = None 87 | super(Handle, self).__init__(lib_) 88 | self.handle_ = handle_ 89 | 90 | def release(self): 91 | if self.handle_ is not None: 92 | self.lib_.tjDestroy(self.handle_) 93 | self.handle_ = None 94 | 95 | 96 | class JPEG(Base): 97 | """Main class. 98 | 99 | Attributes: 100 | decompressor: Handle object for decompressor. 101 | source: numpy array with source data, 102 | either encoded raw jpeg which may be decoded/transformed or 103 | or source image for the later encode. 104 | width: image width. 105 | height: image height. 106 | subsampling: level of chrominance subsampling. 107 | 108 | Static attributes: 109 | decompressors: list of decompressors for caching purposes. 110 | """ 111 | decompressors = [] 112 | 113 | @staticmethod 114 | def clear(): 115 | """Clears internal caches. 116 | """ 117 | # Manually release cached JPEG decompressors 118 | for handle in reversed(JPEG.decompressors): 119 | handle.release() 120 | del JPEG.decompressors[:] 121 | 122 | def __init__(self, source, lib_=None): 123 | """Constructor. 124 | 125 | Parameters: 126 | source: source for JPEG operations (numpy array or file name). 127 | """ 128 | super(JPEG, self).__init__(lib_) 129 | self.decompressor = None 130 | self.width = None 131 | self.height = None 132 | self.subsampling = None 133 | if hasattr(source, "__array_interface__"): 134 | self.source = source 135 | elif numpy.fromfile is not None: 136 | self.source = numpy.fromfile(source, dtype=numpy.uint8) 137 | else: 138 | fin = open(source, "rb") 139 | self.source = numpy.empty(os.path.getsize(source), 140 | dtype=numpy.uint8) 141 | fin.readinto(self.source) 142 | fin.close() 143 | 144 | def _get_decompressor(self): 145 | if self.decompressor is not None: 146 | return 147 | try: 148 | self.decompressor = JPEG.decompressors.pop(-1) 149 | except IndexError: 150 | d = self.lib_.tjInitDecompress() 151 | if d == jpeg.ffi.NULL: 152 | raise JPEGRuntimeError( 153 | "tjInitDecompress() failed with error " 154 | "string %s" % self.get_last_error(), 0) 155 | self.decompressor = Handle(d, self.lib_) 156 | 157 | def parse_header(self): 158 | """Parses JPEG header. 159 | 160 | Fills self.width, self.height, self.subsampling. 161 | """ 162 | self._get_decompressor() 163 | whs = jpeg.ffi.new("int[]", 3) 164 | whs_base = int(jpeg.ffi.cast("size_t", whs)) 165 | whs_itemsize = int(jpeg.ffi.sizeof("int")) 166 | n = self.lib_.tjDecompressHeader2( 167 | self.decompressor.handle_, 168 | jpeg.ffi.cast("unsigned char*", 169 | self.source.__array_interface__["data"][0]), 170 | self.source.nbytes, 171 | jpeg.ffi.cast("int*", whs_base), 172 | jpeg.ffi.cast("int*", whs_base + whs_itemsize), 173 | jpeg.ffi.cast("int*", whs_base + whs_itemsize + whs_itemsize)) 174 | if n: 175 | raise JPEGRuntimeError("tjDecompressHeader2() failed with error " 176 | "%d and error string %s" % 177 | (n, self.get_last_error()), n) 178 | self.width = int(whs[0]) 179 | self.height = int(whs[1]) 180 | self.subsampling = int(whs[2]) 181 | 182 | def decode(self, dst=None, pixfmt=TJPF_RGB): 183 | bpp = jpeg.tjPixelSize[pixfmt] 184 | if dst is None: 185 | if self.width is None: 186 | self.parse_header() 187 | sh = [self.height, self.width] 188 | if bpp > 1: 189 | sh.append(bpp) 190 | dst = numpy.zeros(sh, dtype=numpy.uint8) 191 | elif not hasattr(dst, "__array_interface__"): 192 | raise ValueError("dst should be numpy array or None") 193 | if len(dst.shape) < 2: 194 | raise ValueError("dst shape length should 2 or 3") 195 | if dst.nbytes < dst.shape[1] * dst.shape[0] * bpp: 196 | raise ValueError( 197 | "dst is too small to hold the requested pixel format") 198 | self._get_decompressor() 199 | n = self.lib_.tjDecompress2( 200 | self.decompressor.handle_, 201 | jpeg.ffi.cast("unsigned char*", 202 | self.source.__array_interface__["data"][0]), 203 | self.source.nbytes, 204 | jpeg.ffi.cast("unsigned char*", 205 | dst.__array_interface__["data"][0]), 206 | dst.shape[1], dst.strides[0], dst.shape[0], pixfmt, 0) 207 | if n: 208 | raise JPEGRuntimeError("tjDecompress2() failed with error " 209 | "%d and error string %s" % 210 | (n, self.get_last_error()), n) 211 | return dst 212 | 213 | def __del__(self): 214 | # Return decompressor to cache. 215 | if self.decompressor is not None: 216 | JPEG.decompressors.append(self.decompressor) 217 | --------------------------------------------------------------------------------