PyTurboJPEG 2.1.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PKG-INFO +101 -29
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PyTurboJPEG.egg-info/PKG-INFO +101 -29
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/README.md +100 -28
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/setup.py +1 -1
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/tests/test_turbojpeg.py +62 -1
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/turbojpeg.py +149 -31
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/LICENSE +0 -0
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PyTurboJPEG.egg-info/SOURCES.txt +0 -0
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PyTurboJPEG.egg-info/dependency_links.txt +0 -0
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PyTurboJPEG.egg-info/requires.txt +0 -0
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/PyTurboJPEG.egg-info/top_level.txt +0 -0
- {pyturbojpeg-2.1.0 → pyturbojpeg-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyTurboJPEG
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image.
|
|
5
5
|
Home-page: https://github.com/lilohuang/PyTurboJPEG
|
|
6
6
|
Author: Lilo Huang
|
|
@@ -26,6 +26,11 @@ Dynamic: summary
|
|
|
26
26
|
|
|
27
27
|
A Python wrapper for libjpeg-turbo that enables efficient JPEG image decoding and encoding.
|
|
28
28
|
|
|
29
|
+
[](https://pypi.org/project/pyturbojpeg/)
|
|
30
|
+

|
|
31
|
+
[](https://pypistats.org/packages/pyturbojpeg)
|
|
32
|
+
[](https://github.com/lilohuang/PyTurboJPEG/blob/master/LICENSE)
|
|
33
|
+
|
|
29
34
|
## Prerequisites
|
|
30
35
|
|
|
31
36
|
- [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo/releases) **3.0 or later** (required for PyTurboJPEG 2.0+)
|
|
@@ -239,6 +244,73 @@ cv2.imshow('transposed_image', transposed_image)
|
|
|
239
244
|
cv2.waitKey(0)
|
|
240
245
|
```
|
|
241
246
|
|
|
247
|
+
### ICC Color Management Workflow
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
import io
|
|
251
|
+
import numpy as np
|
|
252
|
+
from PIL import Image, ImageCms
|
|
253
|
+
from turbojpeg import TurboJPEG, TJPF_BGR
|
|
254
|
+
|
|
255
|
+
def decode_jpeg_with_color_management(jpeg_path):
|
|
256
|
+
"""
|
|
257
|
+
Decodes a JPEG and applies color management (ICC Profile to sRGB).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
jpeg_path (str): Path to the input JPEG file.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
PIL.Image: The color-corrected sRGB Image object.
|
|
264
|
+
"""
|
|
265
|
+
# 1. Initialize TurboJPEG
|
|
266
|
+
jpeg = TurboJPEG()
|
|
267
|
+
|
|
268
|
+
with open(jpeg_path, 'rb') as f:
|
|
269
|
+
jpeg_data = f.read()
|
|
270
|
+
|
|
271
|
+
# 2. Get image headers and decode pixels
|
|
272
|
+
# Using TJPF_BGR format (OpenCV standard) for the raw buffer
|
|
273
|
+
width, height, _, _ = jpeg.decode_header(jpeg_data)
|
|
274
|
+
pixels = jpeg.decode(jpeg_data, pixel_format=TJPF_BGR)
|
|
275
|
+
|
|
276
|
+
# 3. Encapsulate into a Pillow Image object
|
|
277
|
+
# Key: Use 'raw' and 'BGR' decoder to correctly map BGR bytes to an RGB Image object
|
|
278
|
+
img = Image.frombytes('RGB', (width, height), pixels, 'raw', 'BGR')
|
|
279
|
+
|
|
280
|
+
# 4. Handle ICC Profile transformation
|
|
281
|
+
try:
|
|
282
|
+
# Extract embedded ICC Profile
|
|
283
|
+
icc_profile = jpeg.get_icc_profile(jpeg_data)
|
|
284
|
+
|
|
285
|
+
if icc_profile:
|
|
286
|
+
# Create Source and Destination Profile objects
|
|
287
|
+
src_profile = ImageCms.getOpenProfile(io.BytesIO(icc_profile))
|
|
288
|
+
dst_profile = ImageCms.createProfile("sRGB")
|
|
289
|
+
|
|
290
|
+
# Perform color transformation (similar to "Convert to Profile" in Photoshop)
|
|
291
|
+
# This step recalculates pixel values to align with sRGB standards
|
|
292
|
+
img = ImageCms.profileToProfile(
|
|
293
|
+
img,
|
|
294
|
+
src_profile,
|
|
295
|
+
dst_profile,
|
|
296
|
+
outputMode='RGB'
|
|
297
|
+
)
|
|
298
|
+
print(f"Successfully applied ICC profile from {jpeg_path}")
|
|
299
|
+
else:
|
|
300
|
+
print("No ICC profile found, assuming sRGB.")
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print(f"Color Management Error: {e}. Returning original raw image.")
|
|
304
|
+
|
|
305
|
+
return img
|
|
306
|
+
|
|
307
|
+
# --- Example Usage ---
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
result_img = decode_jpeg_with_color_management('icc_profile.jpg')
|
|
310
|
+
result_img.show()
|
|
311
|
+
# result_img.save('output_srgb.jpg', quality=95)
|
|
312
|
+
```
|
|
313
|
+
|
|
242
314
|
## High-Precision JPEG Support
|
|
243
315
|
|
|
244
316
|
PyTurboJPEG 2.0+ supports 12-bit and 16-bit precision JPEG encoding and decoding using libjpeg-turbo 3.0+ APIs. This feature is ideal for medical imaging, scientific photography, and other applications requiring higher bit depth.
|
|
@@ -278,7 +350,34 @@ with open('output_12bit.jpg', 'rb') as f:
|
|
|
278
350
|
decoded_from_file = jpeg.decode_12bit(f.read())
|
|
279
351
|
```
|
|
280
352
|
|
|
281
|
-
###
|
|
353
|
+
### Lossless JPEG for 12-bit and 16-bit
|
|
354
|
+
|
|
355
|
+
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
356
|
+
|
|
357
|
+
#### 12-bit Lossless JPEG
|
|
358
|
+
|
|
359
|
+
12-bit precision with lossless compression:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
import numpy as np
|
|
363
|
+
from turbojpeg import TurboJPEG
|
|
364
|
+
|
|
365
|
+
jpeg = TurboJPEG()
|
|
366
|
+
|
|
367
|
+
# Create 12-bit image
|
|
368
|
+
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
369
|
+
|
|
370
|
+
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
371
|
+
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
372
|
+
|
|
373
|
+
# Decode using decode_12bit()
|
|
374
|
+
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
375
|
+
|
|
376
|
+
# Perfect reconstruction
|
|
377
|
+
assert np.array_equal(img_12bit, decoded_img) # True
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### 16-bit Lossless JPEG
|
|
282
381
|
|
|
283
382
|
16-bit JPEG provides the highest precision with perfect reconstruction through lossless compression. The JPEG standard only supports 16-bit for lossless mode.
|
|
284
383
|
|
|
@@ -309,33 +408,6 @@ with open('output_16bit_lossless.jpg', 'rb') as f:
|
|
|
309
408
|
decoded_from_file = jpeg.decode_16bit(f.read())
|
|
310
409
|
```
|
|
311
410
|
|
|
312
|
-
### Lossless JPEG for 12-bit and 16-bit
|
|
313
|
-
|
|
314
|
-
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
315
|
-
|
|
316
|
-
#### 12-bit Lossless JPEG
|
|
317
|
-
|
|
318
|
-
12-bit precision with lossless compression:
|
|
319
|
-
|
|
320
|
-
```python
|
|
321
|
-
import numpy as np
|
|
322
|
-
from turbojpeg import TurboJPEG
|
|
323
|
-
|
|
324
|
-
jpeg = TurboJPEG()
|
|
325
|
-
|
|
326
|
-
# Create 12-bit image
|
|
327
|
-
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
328
|
-
|
|
329
|
-
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
330
|
-
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
331
|
-
|
|
332
|
-
# Decode using decode_12bit()
|
|
333
|
-
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
334
|
-
|
|
335
|
-
# Perfect reconstruction
|
|
336
|
-
assert np.array_equal(img_12bit, decoded_img) # True
|
|
337
|
-
```
|
|
338
|
-
|
|
339
411
|
### Medical and Scientific Imaging
|
|
340
412
|
|
|
341
413
|
For medical and scientific applications, 12-bit JPEG provides excellent precision while maintaining file size efficiency:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyTurboJPEG
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image.
|
|
5
5
|
Home-page: https://github.com/lilohuang/PyTurboJPEG
|
|
6
6
|
Author: Lilo Huang
|
|
@@ -26,6 +26,11 @@ Dynamic: summary
|
|
|
26
26
|
|
|
27
27
|
A Python wrapper for libjpeg-turbo that enables efficient JPEG image decoding and encoding.
|
|
28
28
|
|
|
29
|
+
[](https://pypi.org/project/pyturbojpeg/)
|
|
30
|
+

|
|
31
|
+
[](https://pypistats.org/packages/pyturbojpeg)
|
|
32
|
+
[](https://github.com/lilohuang/PyTurboJPEG/blob/master/LICENSE)
|
|
33
|
+
|
|
29
34
|
## Prerequisites
|
|
30
35
|
|
|
31
36
|
- [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo/releases) **3.0 or later** (required for PyTurboJPEG 2.0+)
|
|
@@ -239,6 +244,73 @@ cv2.imshow('transposed_image', transposed_image)
|
|
|
239
244
|
cv2.waitKey(0)
|
|
240
245
|
```
|
|
241
246
|
|
|
247
|
+
### ICC Color Management Workflow
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
import io
|
|
251
|
+
import numpy as np
|
|
252
|
+
from PIL import Image, ImageCms
|
|
253
|
+
from turbojpeg import TurboJPEG, TJPF_BGR
|
|
254
|
+
|
|
255
|
+
def decode_jpeg_with_color_management(jpeg_path):
|
|
256
|
+
"""
|
|
257
|
+
Decodes a JPEG and applies color management (ICC Profile to sRGB).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
jpeg_path (str): Path to the input JPEG file.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
PIL.Image: The color-corrected sRGB Image object.
|
|
264
|
+
"""
|
|
265
|
+
# 1. Initialize TurboJPEG
|
|
266
|
+
jpeg = TurboJPEG()
|
|
267
|
+
|
|
268
|
+
with open(jpeg_path, 'rb') as f:
|
|
269
|
+
jpeg_data = f.read()
|
|
270
|
+
|
|
271
|
+
# 2. Get image headers and decode pixels
|
|
272
|
+
# Using TJPF_BGR format (OpenCV standard) for the raw buffer
|
|
273
|
+
width, height, _, _ = jpeg.decode_header(jpeg_data)
|
|
274
|
+
pixels = jpeg.decode(jpeg_data, pixel_format=TJPF_BGR)
|
|
275
|
+
|
|
276
|
+
# 3. Encapsulate into a Pillow Image object
|
|
277
|
+
# Key: Use 'raw' and 'BGR' decoder to correctly map BGR bytes to an RGB Image object
|
|
278
|
+
img = Image.frombytes('RGB', (width, height), pixels, 'raw', 'BGR')
|
|
279
|
+
|
|
280
|
+
# 4. Handle ICC Profile transformation
|
|
281
|
+
try:
|
|
282
|
+
# Extract embedded ICC Profile
|
|
283
|
+
icc_profile = jpeg.get_icc_profile(jpeg_data)
|
|
284
|
+
|
|
285
|
+
if icc_profile:
|
|
286
|
+
# Create Source and Destination Profile objects
|
|
287
|
+
src_profile = ImageCms.getOpenProfile(io.BytesIO(icc_profile))
|
|
288
|
+
dst_profile = ImageCms.createProfile("sRGB")
|
|
289
|
+
|
|
290
|
+
# Perform color transformation (similar to "Convert to Profile" in Photoshop)
|
|
291
|
+
# This step recalculates pixel values to align with sRGB standards
|
|
292
|
+
img = ImageCms.profileToProfile(
|
|
293
|
+
img,
|
|
294
|
+
src_profile,
|
|
295
|
+
dst_profile,
|
|
296
|
+
outputMode='RGB'
|
|
297
|
+
)
|
|
298
|
+
print(f"Successfully applied ICC profile from {jpeg_path}")
|
|
299
|
+
else:
|
|
300
|
+
print("No ICC profile found, assuming sRGB.")
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print(f"Color Management Error: {e}. Returning original raw image.")
|
|
304
|
+
|
|
305
|
+
return img
|
|
306
|
+
|
|
307
|
+
# --- Example Usage ---
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
result_img = decode_jpeg_with_color_management('icc_profile.jpg')
|
|
310
|
+
result_img.show()
|
|
311
|
+
# result_img.save('output_srgb.jpg', quality=95)
|
|
312
|
+
```
|
|
313
|
+
|
|
242
314
|
## High-Precision JPEG Support
|
|
243
315
|
|
|
244
316
|
PyTurboJPEG 2.0+ supports 12-bit and 16-bit precision JPEG encoding and decoding using libjpeg-turbo 3.0+ APIs. This feature is ideal for medical imaging, scientific photography, and other applications requiring higher bit depth.
|
|
@@ -278,7 +350,34 @@ with open('output_12bit.jpg', 'rb') as f:
|
|
|
278
350
|
decoded_from_file = jpeg.decode_12bit(f.read())
|
|
279
351
|
```
|
|
280
352
|
|
|
281
|
-
###
|
|
353
|
+
### Lossless JPEG for 12-bit and 16-bit
|
|
354
|
+
|
|
355
|
+
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
356
|
+
|
|
357
|
+
#### 12-bit Lossless JPEG
|
|
358
|
+
|
|
359
|
+
12-bit precision with lossless compression:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
import numpy as np
|
|
363
|
+
from turbojpeg import TurboJPEG
|
|
364
|
+
|
|
365
|
+
jpeg = TurboJPEG()
|
|
366
|
+
|
|
367
|
+
# Create 12-bit image
|
|
368
|
+
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
369
|
+
|
|
370
|
+
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
371
|
+
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
372
|
+
|
|
373
|
+
# Decode using decode_12bit()
|
|
374
|
+
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
375
|
+
|
|
376
|
+
# Perfect reconstruction
|
|
377
|
+
assert np.array_equal(img_12bit, decoded_img) # True
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### 16-bit Lossless JPEG
|
|
282
381
|
|
|
283
382
|
16-bit JPEG provides the highest precision with perfect reconstruction through lossless compression. The JPEG standard only supports 16-bit for lossless mode.
|
|
284
383
|
|
|
@@ -309,33 +408,6 @@ with open('output_16bit_lossless.jpg', 'rb') as f:
|
|
|
309
408
|
decoded_from_file = jpeg.decode_16bit(f.read())
|
|
310
409
|
```
|
|
311
410
|
|
|
312
|
-
### Lossless JPEG for 12-bit and 16-bit
|
|
313
|
-
|
|
314
|
-
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
315
|
-
|
|
316
|
-
#### 12-bit Lossless JPEG
|
|
317
|
-
|
|
318
|
-
12-bit precision with lossless compression:
|
|
319
|
-
|
|
320
|
-
```python
|
|
321
|
-
import numpy as np
|
|
322
|
-
from turbojpeg import TurboJPEG
|
|
323
|
-
|
|
324
|
-
jpeg = TurboJPEG()
|
|
325
|
-
|
|
326
|
-
# Create 12-bit image
|
|
327
|
-
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
328
|
-
|
|
329
|
-
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
330
|
-
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
331
|
-
|
|
332
|
-
# Decode using decode_12bit()
|
|
333
|
-
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
334
|
-
|
|
335
|
-
# Perfect reconstruction
|
|
336
|
-
assert np.array_equal(img_12bit, decoded_img) # True
|
|
337
|
-
```
|
|
338
|
-
|
|
339
411
|
### Medical and Scientific Imaging
|
|
340
412
|
|
|
341
413
|
For medical and scientific applications, 12-bit JPEG provides excellent precision while maintaining file size efficiency:
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
A Python wrapper for libjpeg-turbo that enables efficient JPEG image decoding and encoding.
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/pyturbojpeg/)
|
|
6
|
+

|
|
7
|
+
[](https://pypistats.org/packages/pyturbojpeg)
|
|
8
|
+
[](https://github.com/lilohuang/PyTurboJPEG/blob/master/LICENSE)
|
|
9
|
+
|
|
5
10
|
## Prerequisites
|
|
6
11
|
|
|
7
12
|
- [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo/releases) **3.0 or later** (required for PyTurboJPEG 2.0+)
|
|
@@ -215,6 +220,73 @@ cv2.imshow('transposed_image', transposed_image)
|
|
|
215
220
|
cv2.waitKey(0)
|
|
216
221
|
```
|
|
217
222
|
|
|
223
|
+
### ICC Color Management Workflow
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
import io
|
|
227
|
+
import numpy as np
|
|
228
|
+
from PIL import Image, ImageCms
|
|
229
|
+
from turbojpeg import TurboJPEG, TJPF_BGR
|
|
230
|
+
|
|
231
|
+
def decode_jpeg_with_color_management(jpeg_path):
|
|
232
|
+
"""
|
|
233
|
+
Decodes a JPEG and applies color management (ICC Profile to sRGB).
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
jpeg_path (str): Path to the input JPEG file.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
PIL.Image: The color-corrected sRGB Image object.
|
|
240
|
+
"""
|
|
241
|
+
# 1. Initialize TurboJPEG
|
|
242
|
+
jpeg = TurboJPEG()
|
|
243
|
+
|
|
244
|
+
with open(jpeg_path, 'rb') as f:
|
|
245
|
+
jpeg_data = f.read()
|
|
246
|
+
|
|
247
|
+
# 2. Get image headers and decode pixels
|
|
248
|
+
# Using TJPF_BGR format (OpenCV standard) for the raw buffer
|
|
249
|
+
width, height, _, _ = jpeg.decode_header(jpeg_data)
|
|
250
|
+
pixels = jpeg.decode(jpeg_data, pixel_format=TJPF_BGR)
|
|
251
|
+
|
|
252
|
+
# 3. Encapsulate into a Pillow Image object
|
|
253
|
+
# Key: Use 'raw' and 'BGR' decoder to correctly map BGR bytes to an RGB Image object
|
|
254
|
+
img = Image.frombytes('RGB', (width, height), pixels, 'raw', 'BGR')
|
|
255
|
+
|
|
256
|
+
# 4. Handle ICC Profile transformation
|
|
257
|
+
try:
|
|
258
|
+
# Extract embedded ICC Profile
|
|
259
|
+
icc_profile = jpeg.get_icc_profile(jpeg_data)
|
|
260
|
+
|
|
261
|
+
if icc_profile:
|
|
262
|
+
# Create Source and Destination Profile objects
|
|
263
|
+
src_profile = ImageCms.getOpenProfile(io.BytesIO(icc_profile))
|
|
264
|
+
dst_profile = ImageCms.createProfile("sRGB")
|
|
265
|
+
|
|
266
|
+
# Perform color transformation (similar to "Convert to Profile" in Photoshop)
|
|
267
|
+
# This step recalculates pixel values to align with sRGB standards
|
|
268
|
+
img = ImageCms.profileToProfile(
|
|
269
|
+
img,
|
|
270
|
+
src_profile,
|
|
271
|
+
dst_profile,
|
|
272
|
+
outputMode='RGB'
|
|
273
|
+
)
|
|
274
|
+
print(f"Successfully applied ICC profile from {jpeg_path}")
|
|
275
|
+
else:
|
|
276
|
+
print("No ICC profile found, assuming sRGB.")
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
print(f"Color Management Error: {e}. Returning original raw image.")
|
|
280
|
+
|
|
281
|
+
return img
|
|
282
|
+
|
|
283
|
+
# --- Example Usage ---
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
result_img = decode_jpeg_with_color_management('icc_profile.jpg')
|
|
286
|
+
result_img.show()
|
|
287
|
+
# result_img.save('output_srgb.jpg', quality=95)
|
|
288
|
+
```
|
|
289
|
+
|
|
218
290
|
## High-Precision JPEG Support
|
|
219
291
|
|
|
220
292
|
PyTurboJPEG 2.0+ supports 12-bit and 16-bit precision JPEG encoding and decoding using libjpeg-turbo 3.0+ APIs. This feature is ideal for medical imaging, scientific photography, and other applications requiring higher bit depth.
|
|
@@ -254,7 +326,34 @@ with open('output_12bit.jpg', 'rb') as f:
|
|
|
254
326
|
decoded_from_file = jpeg.decode_12bit(f.read())
|
|
255
327
|
```
|
|
256
328
|
|
|
257
|
-
###
|
|
329
|
+
### Lossless JPEG for 12-bit and 16-bit
|
|
330
|
+
|
|
331
|
+
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
332
|
+
|
|
333
|
+
#### 12-bit Lossless JPEG
|
|
334
|
+
|
|
335
|
+
12-bit precision with lossless compression:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
import numpy as np
|
|
339
|
+
from turbojpeg import TurboJPEG
|
|
340
|
+
|
|
341
|
+
jpeg = TurboJPEG()
|
|
342
|
+
|
|
343
|
+
# Create 12-bit image
|
|
344
|
+
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
345
|
+
|
|
346
|
+
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
347
|
+
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
348
|
+
|
|
349
|
+
# Decode using decode_12bit()
|
|
350
|
+
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
351
|
+
|
|
352
|
+
# Perfect reconstruction
|
|
353
|
+
assert np.array_equal(img_12bit, decoded_img) # True
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### 16-bit Lossless JPEG
|
|
258
357
|
|
|
259
358
|
16-bit JPEG provides the highest precision with perfect reconstruction through lossless compression. The JPEG standard only supports 16-bit for lossless mode.
|
|
260
359
|
|
|
@@ -285,33 +384,6 @@ with open('output_16bit_lossless.jpg', 'rb') as f:
|
|
|
285
384
|
decoded_from_file = jpeg.decode_16bit(f.read())
|
|
286
385
|
```
|
|
287
386
|
|
|
288
|
-
### Lossless JPEG for 12-bit and 16-bit
|
|
289
|
-
|
|
290
|
-
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
|
|
291
|
-
|
|
292
|
-
#### 12-bit Lossless JPEG
|
|
293
|
-
|
|
294
|
-
12-bit precision with lossless compression:
|
|
295
|
-
|
|
296
|
-
```python
|
|
297
|
-
import numpy as np
|
|
298
|
-
from turbojpeg import TurboJPEG
|
|
299
|
-
|
|
300
|
-
jpeg = TurboJPEG()
|
|
301
|
-
|
|
302
|
-
# Create 12-bit image
|
|
303
|
-
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
|
|
304
|
-
|
|
305
|
-
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
|
|
306
|
-
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
|
|
307
|
-
|
|
308
|
-
# Decode using decode_12bit()
|
|
309
|
-
decoded_img = jpeg.decode_12bit(jpeg_data)
|
|
310
|
-
|
|
311
|
-
# Perfect reconstruction
|
|
312
|
-
assert np.array_equal(img_12bit, decoded_img) # True
|
|
313
|
-
```
|
|
314
|
-
|
|
315
387
|
### Medical and Scientific Imaging
|
|
316
388
|
|
|
317
389
|
For medical and scientific applications, 12-bit JPEG provides excellent precision while maintaining file size efficiency:
|
|
@@ -2,7 +2,7 @@ import io
|
|
|
2
2
|
from setuptools import setup, find_packages
|
|
3
3
|
setup(
|
|
4
4
|
name='PyTurboJPEG',
|
|
5
|
-
version='2.
|
|
5
|
+
version='2.3.0',
|
|
6
6
|
description='A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image.',
|
|
7
7
|
author='Lilo Huang',
|
|
8
8
|
author_email='kuso.cc@gmail.com',
|
|
@@ -30,7 +30,8 @@ from turbojpeg import (
|
|
|
30
30
|
TJSAMP_444, TJSAMP_422, TJSAMP_420, TJSAMP_GRAY, TJSAMP_440, TJSAMP_411,
|
|
31
31
|
TJCS_RGB, TJCS_YCbCr, TJCS_GRAY,
|
|
32
32
|
TJFLAG_PROGRESSIVE, TJFLAG_FASTUPSAMPLE, TJFLAG_FASTDCT,
|
|
33
|
-
TJPRECISION_8, TJPRECISION_12, TJPRECISION_16
|
|
33
|
+
TJPRECISION_8, TJPRECISION_12, TJPRECISION_16,
|
|
34
|
+
TJPARAM_SAVEMARKERS
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
|
|
@@ -1802,5 +1803,65 @@ class TestLosslessJPEG:
|
|
|
1802
1803
|
assert np.array_equal(img_gray, decoded_gray), "Lossless grayscale should be perfect"
|
|
1803
1804
|
|
|
1804
1805
|
|
|
1806
|
+
import struct
|
|
1807
|
+
|
|
1808
|
+
def _make_minimal_srgb_icc():
|
|
1809
|
+
"""Build a minimal but structurally valid ICC profile for sRGB."""
|
|
1810
|
+
profile = bytearray(132)
|
|
1811
|
+
struct.pack_into('>I', profile, 0, 132)
|
|
1812
|
+
struct.pack_into('>I', profile, 8, 0x02100000)
|
|
1813
|
+
profile[12:16] = b'mntr'
|
|
1814
|
+
profile[16:20] = b'RGB '
|
|
1815
|
+
profile[20:24] = b'XYZ '
|
|
1816
|
+
profile[36:40] = b'acsp'
|
|
1817
|
+
return bytes(profile)
|
|
1818
|
+
|
|
1819
|
+
MINIMAL_SRGB_ICC = _make_minimal_srgb_icc()
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
class TestICCProfile:
|
|
1823
|
+
"""Test ICC profile embed/extract functionality (requires TurboJPEG 3.1+)."""
|
|
1824
|
+
|
|
1825
|
+
@pytest.fixture(autouse=True)
|
|
1826
|
+
def require_icc_support(self, jpeg_instance):
|
|
1827
|
+
"""Skip the test if the loaded libturbojpeg does not support ICC profiles."""
|
|
1828
|
+
try:
|
|
1829
|
+
jpeg_instance.get_icc_profile(b'\xff\xd8\xff\xd9')
|
|
1830
|
+
except NotImplementedError as e:
|
|
1831
|
+
pytest.skip(str(e))
|
|
1832
|
+
except (OSError, Exception):
|
|
1833
|
+
pass # Header parse error is fine — the function exists
|
|
1834
|
+
|
|
1835
|
+
def test_get_icc_profile_with_embedded_profile(self, jpeg_instance, sample_bgr_image):
|
|
1836
|
+
"""Test that get_icc_profile returns non-empty bytes for a JPEG with ICC profile."""
|
|
1837
|
+
jpeg_with_icc = jpeg_instance.encode(
|
|
1838
|
+
sample_bgr_image, quality=85, icc_profile=MINIMAL_SRGB_ICC)
|
|
1839
|
+
icc = jpeg_instance.get_icc_profile(jpeg_with_icc)
|
|
1840
|
+
assert icc is not None, "Expected ICC profile but got None"
|
|
1841
|
+
assert isinstance(icc, bytes), "ICC profile should be bytes"
|
|
1842
|
+
assert len(icc) > 0, "ICC profile should be non-empty"
|
|
1843
|
+
assert icc == MINIMAL_SRGB_ICC, "Extracted ICC profile should match embedded profile"
|
|
1844
|
+
|
|
1845
|
+
def test_get_icc_profile_without_profile(self, jpeg_instance, sample_bgr_image):
|
|
1846
|
+
"""Test that get_icc_profile returns None for a JPEG without ICC profile."""
|
|
1847
|
+
jpeg_without_icc = jpeg_instance.encode(sample_bgr_image, quality=85)
|
|
1848
|
+
icc = jpeg_instance.get_icc_profile(jpeg_without_icc)
|
|
1849
|
+
assert icc is None, \
|
|
1850
|
+
"Expected None for JPEG without ICC profile"
|
|
1851
|
+
|
|
1852
|
+
def test_icc_profile_roundtrip(self, jpeg_instance, sample_bgr_image):
|
|
1853
|
+
"""Test ICC profile survives a full encode→decode_header roundtrip."""
|
|
1854
|
+
jpeg_data = jpeg_instance.encode(
|
|
1855
|
+
sample_bgr_image, quality=95,
|
|
1856
|
+
jpeg_subsample=TJSAMP_444,
|
|
1857
|
+
icc_profile=MINIMAL_SRGB_ICC)
|
|
1858
|
+
assert jpeg_data is not None
|
|
1859
|
+
assert len(jpeg_data) > 0
|
|
1860
|
+
extracted_icc = jpeg_instance.get_icc_profile(jpeg_data)
|
|
1861
|
+
assert extracted_icc is not None, "ICC profile missing after roundtrip"
|
|
1862
|
+
assert extracted_icc == MINIMAL_SRGB_ICC, \
|
|
1863
|
+
f"ICC profile mismatch: expected {len(MINIMAL_SRGB_ICC)} bytes, got {len(extracted_icc) if extracted_icc else 0}"
|
|
1864
|
+
|
|
1865
|
+
|
|
1805
1866
|
if __name__ == '__main__':
|
|
1806
1867
|
pytest.main([__file__, '-v'])
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
# SOFTWARE.
|
|
24
24
|
|
|
25
25
|
__author__ = 'Lilo Huang <kuso.cc@gmail.com>'
|
|
26
|
-
__version__ = '2.
|
|
26
|
+
__version__ = '2.3.0'
|
|
27
27
|
|
|
28
28
|
from ctypes import *
|
|
29
29
|
from ctypes.util import find_library
|
|
@@ -192,6 +192,7 @@ TJPARAM_YDENSITY = 21
|
|
|
192
192
|
TJPARAM_DENSITYUNITS = 22
|
|
193
193
|
TJPARAM_MAXPIXELS = 23
|
|
194
194
|
TJPARAM_MAXMEMORY = 24
|
|
195
|
+
TJPARAM_SAVEMARKERS = 25
|
|
195
196
|
|
|
196
197
|
class CroppingRegion(Structure):
|
|
197
198
|
_fields_ = [("x", c_int), ("y", c_int), ("w", c_int), ("h", c_int)]
|
|
@@ -304,12 +305,12 @@ def fill_background(coeffs_ptr, arrayRegion, planeRegion, componentID, transform
|
|
|
304
305
|
min(arrayRegion.y+arrayRegion.h, background_data.h)
|
|
305
306
|
- arrayRegion.y
|
|
306
307
|
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
308
|
+
y_start = left_start_row // MCU_HEIGHT
|
|
309
|
+
y_end = left_end_row // MCU_HEIGHT
|
|
310
|
+
x_start = background_data.w // MCU_WIDTH
|
|
311
|
+
x_end = planeRegion.w // MCU_WIDTH
|
|
312
|
+
if y_end > y_start and x_end > x_start:
|
|
313
|
+
coeffs[y_start:y_end, x_start:x_end, 0] = background_data.lum
|
|
313
314
|
|
|
314
315
|
# fill mcus under image
|
|
315
316
|
bottom_start_row = (
|
|
@@ -319,23 +320,20 @@ def fill_background(coeffs_ptr, arrayRegion, planeRegion, componentID, transform
|
|
|
319
320
|
max(arrayRegion.y+arrayRegion.h, background_data.h)
|
|
320
321
|
- arrayRegion.y
|
|
321
322
|
)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
coeffs[y][x][0] = background_data.lum
|
|
323
|
+
y_start = bottom_start_row // MCU_HEIGHT
|
|
324
|
+
y_end = bottom_end_row // MCU_HEIGHT
|
|
325
|
+
x_end = planeRegion.w // MCU_WIDTH
|
|
326
|
+
if y_end > y_start and x_end > 0:
|
|
327
|
+
coeffs[y_start:y_end, 0:x_end, 0] = background_data.lum
|
|
328
328
|
|
|
329
329
|
return 1
|
|
330
330
|
|
|
331
|
-
|
|
332
331
|
def split_byte_into_nibbles(value):
|
|
333
332
|
"""Split byte int into 2 nibbles (4 bits)."""
|
|
334
333
|
first = value >> 4
|
|
335
334
|
second = value & 0x0F
|
|
336
335
|
return first, second
|
|
337
336
|
|
|
338
|
-
|
|
339
337
|
class TurboJPEG(object):
|
|
340
338
|
"""A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image."""
|
|
341
339
|
def __init__(self, lib_path=None):
|
|
@@ -489,6 +487,17 @@ class TurboJPEG(object):
|
|
|
489
487
|
self.__decompress16.argtypes = [
|
|
490
488
|
c_void_p, POINTER(c_ubyte), c_size_t, POINTER(c_ushort), c_int, c_int]
|
|
491
489
|
self.__decompress16.restype = c_int
|
|
490
|
+
|
|
491
|
+
# tjGetScalingFactors
|
|
492
|
+
get_scaling_factors = turbo_jpeg.tjGetScalingFactors
|
|
493
|
+
get_scaling_factors.argtypes = [POINTER(c_int)]
|
|
494
|
+
get_scaling_factors.restype = POINTER(ScalingFactor)
|
|
495
|
+
num_scaling_factors = c_int()
|
|
496
|
+
scaling_factors = get_scaling_factors(byref(num_scaling_factors))
|
|
497
|
+
self.__scaling_factors = frozenset(
|
|
498
|
+
(scaling_factors[i].num, scaling_factors[i].denom)
|
|
499
|
+
for i in range(num_scaling_factors.value)
|
|
500
|
+
)
|
|
492
501
|
|
|
493
502
|
# tj3CompressFromYUV16 - compress 16-bit YUV to JPEG (TurboJPEG 3.1+)
|
|
494
503
|
# These functions may not be available in all TurboJPEG 3.x versions
|
|
@@ -519,16 +528,21 @@ class TurboJPEG(object):
|
|
|
519
528
|
except AttributeError:
|
|
520
529
|
self.__decompressToYUVPlanes16 = None
|
|
521
530
|
|
|
522
|
-
#
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
# tj3GetICCProfile - retrieve ICC profile from decompressor after header parsing (TurboJPEG 3.1+)
|
|
532
|
+
try:
|
|
533
|
+
self.__get_icc_profile = turbo_jpeg.tj3GetICCProfile
|
|
534
|
+
self.__get_icc_profile.argtypes = [c_void_p, POINTER(c_void_p), POINTER(c_size_t)]
|
|
535
|
+
self.__get_icc_profile.restype = c_int
|
|
536
|
+
except AttributeError:
|
|
537
|
+
self.__get_icc_profile = None
|
|
538
|
+
|
|
539
|
+
# tj3SetICCProfile - attach ICC profile to compressor before compression (TurboJPEG 3.1+)
|
|
540
|
+
try:
|
|
541
|
+
self.__set_icc_profile = turbo_jpeg.tj3SetICCProfile
|
|
542
|
+
self.__set_icc_profile.argtypes = [c_void_p, c_void_p, c_size_t]
|
|
543
|
+
self.__set_icc_profile.restype = c_int
|
|
544
|
+
except AttributeError:
|
|
545
|
+
self.__set_icc_profile = None
|
|
532
546
|
|
|
533
547
|
def decode_header(self, jpeg_buf, return_precision=False):
|
|
534
548
|
"""decodes JPEG header and returns image properties as a tuple.
|
|
@@ -592,6 +606,101 @@ class TurboJPEG(object):
|
|
|
592
606
|
finally:
|
|
593
607
|
self.__destroy(handle)
|
|
594
608
|
|
|
609
|
+
def get_icc_profile(self, jpeg_buf):
|
|
610
|
+
"""Extracts the embedded ICC color profile from a JPEG image.
|
|
611
|
+
|
|
612
|
+
Requires TurboJPEG 3.1 or later with tj3GetICCProfile support.
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
jpeg_buf : bytes
|
|
617
|
+
JPEG image data buffer containing an embedded ICC profile.
|
|
618
|
+
|
|
619
|
+
Returns
|
|
620
|
+
-------
|
|
621
|
+
bytes or None
|
|
622
|
+
Raw ICC profile data as a bytes object, or None if no ICC profile
|
|
623
|
+
is present in the JPEG stream.
|
|
624
|
+
|
|
625
|
+
Raises
|
|
626
|
+
------
|
|
627
|
+
OSError
|
|
628
|
+
If the JPEG header cannot be parsed or a fatal error occurs.
|
|
629
|
+
NotImplementedError
|
|
630
|
+
If the loaded libturbojpeg does not export tj3GetICCProfile.
|
|
631
|
+
|
|
632
|
+
Examples
|
|
633
|
+
--------
|
|
634
|
+
>>> jpeg = TurboJPEG()
|
|
635
|
+
>>> with open('photo_with_icc.jpg', 'rb') as f:
|
|
636
|
+
... data = f.read()
|
|
637
|
+
>>> icc = jpeg.get_icc_profile(data)
|
|
638
|
+
>>> if icc:
|
|
639
|
+
... print(f'ICC profile size: {len(icc)} bytes')
|
|
640
|
+
"""
|
|
641
|
+
if self.__get_icc_profile is None:
|
|
642
|
+
raise NotImplementedError(
|
|
643
|
+
'tj3GetICCProfile is not available in the loaded libturbojpeg. '
|
|
644
|
+
'Please upgrade to libjpeg-turbo 3.1 or later.')
|
|
645
|
+
handle = self.__init(TJINIT_DECOMPRESS)
|
|
646
|
+
try:
|
|
647
|
+
# Set TJPARAM_SAVEMARKERS to 2 (APP2) so the decompressor
|
|
648
|
+
# retains ICC profile markers during header parsing.
|
|
649
|
+
if self.__set(handle, TJPARAM_SAVEMARKERS, 2) != 0:
|
|
650
|
+
self.__report_error(handle)
|
|
651
|
+
jpeg_array = np.frombuffer(jpeg_buf, dtype=np.uint8)
|
|
652
|
+
src_addr = self.__getaddr(jpeg_array)
|
|
653
|
+
status = self.__decompress_header(handle, src_addr, jpeg_array.size)
|
|
654
|
+
if status != 0:
|
|
655
|
+
self.__report_error(handle)
|
|
656
|
+
icc_buf = c_void_p()
|
|
657
|
+
icc_size = c_size_t()
|
|
658
|
+
status = self.__get_icc_profile(handle, byref(icc_buf), byref(icc_size))
|
|
659
|
+
if status != 0:
|
|
660
|
+
# A non-fatal return (e.g. no profile present) should return None
|
|
661
|
+
err_code = self.__get_error_code(handle)
|
|
662
|
+
if err_code == TJERR_WARNING:
|
|
663
|
+
return None
|
|
664
|
+
self.__report_error(handle)
|
|
665
|
+
if icc_buf.value is None or icc_size.value == 0:
|
|
666
|
+
return None
|
|
667
|
+
result = self.__copy_from_buffer(icc_buf.value, icc_size.value)
|
|
668
|
+
self.__free(icc_buf)
|
|
669
|
+
return result
|
|
670
|
+
finally:
|
|
671
|
+
self.__destroy(handle)
|
|
672
|
+
|
|
673
|
+
def set_icc_profile(self, handle, icc_buf):
|
|
674
|
+
"""Attaches an ICC color profile to an active compressor handle.
|
|
675
|
+
|
|
676
|
+
This is a low-level helper intended for use when building custom
|
|
677
|
+
compression pipelines. In most cases, use encode() with the
|
|
678
|
+
icc_profile parameter instead.
|
|
679
|
+
|
|
680
|
+
Parameters
|
|
681
|
+
----------
|
|
682
|
+
handle : ctypes void pointer
|
|
683
|
+
An active TurboJPEG compressor handle (TJINIT_COMPRESS).
|
|
684
|
+
icc_buf : bytes
|
|
685
|
+
Raw ICC profile data to embed.
|
|
686
|
+
|
|
687
|
+
Raises
|
|
688
|
+
------
|
|
689
|
+
OSError
|
|
690
|
+
If tj3SetICCProfile returns a non-zero status.
|
|
691
|
+
NotImplementedError
|
|
692
|
+
If the loaded libturbojpeg does not export tj3SetICCProfile.
|
|
693
|
+
"""
|
|
694
|
+
if self.__set_icc_profile is None:
|
|
695
|
+
raise NotImplementedError(
|
|
696
|
+
'tj3SetICCProfile is not available in the loaded libturbojpeg. '
|
|
697
|
+
'Please upgrade to libjpeg-turbo 3.1 or later.')
|
|
698
|
+
icc_array = np.frombuffer(icc_buf, dtype=np.uint8)
|
|
699
|
+
icc_addr = self.__getaddr(icc_array)
|
|
700
|
+
status = self.__set_icc_profile(handle, icc_addr, len(icc_buf))
|
|
701
|
+
if status != 0:
|
|
702
|
+
self.__report_error(handle)
|
|
703
|
+
|
|
595
704
|
def decode(self, jpeg_buf, pixel_format=TJPF_BGR, scaling_factor=None, flags=0, dst=None):
|
|
596
705
|
"""decodes JPEG memory buffer to numpy array.
|
|
597
706
|
|
|
@@ -707,7 +816,7 @@ class TurboJPEG(object):
|
|
|
707
816
|
finally:
|
|
708
817
|
self.__destroy(handle)
|
|
709
818
|
|
|
710
|
-
def encode(self, img_array, quality=85, pixel_format=TJPF_BGR, jpeg_subsample=TJSAMP_422, flags=0, dst=None, lossless=False):
|
|
819
|
+
def encode(self, img_array, quality=85, pixel_format=TJPF_BGR, jpeg_subsample=TJSAMP_422, flags=0, dst=None, lossless=False, icc_profile=None):
|
|
711
820
|
"""encodes numpy array to JPEG memory buffer.
|
|
712
821
|
|
|
713
822
|
Parameters
|
|
@@ -729,6 +838,9 @@ class TurboJPEG(object):
|
|
|
729
838
|
When True, provides perfect reconstruction with larger file sizes.
|
|
730
839
|
Note: quality and jpeg_subsample parameters are ignored in lossless mode;
|
|
731
840
|
subsampling is automatically set to 4:4:4 by the library.
|
|
841
|
+
icc_profile : bytes or None
|
|
842
|
+
Raw ICC profile data to embed in the JPEG (optional).
|
|
843
|
+
Requires TurboJPEG 3.1 or later with tj3SetICCProfile support.
|
|
732
844
|
|
|
733
845
|
Returns
|
|
734
846
|
-------
|
|
@@ -763,6 +875,9 @@ class TurboJPEG(object):
|
|
|
763
875
|
if img_array.dtype != np.uint8:
|
|
764
876
|
raise ValueError('encode() requires uint8 array (values 0-255); use encode_12bit() for 12-bit images (uint16, 0-4095) or encode_16bit() for 16-bit images (uint16, 0-65535)')
|
|
765
877
|
|
|
878
|
+
if icc_profile is not None:
|
|
879
|
+
self.set_icc_profile(handle, icc_profile)
|
|
880
|
+
|
|
766
881
|
if dst is not None and not self.__is_buffer(dst):
|
|
767
882
|
raise TypeError('\'dst\' argument must support buffer protocol')
|
|
768
883
|
if (dst is not None and
|
|
@@ -1195,6 +1310,8 @@ class TurboJPEG(object):
|
|
|
1195
1310
|
|
|
1196
1311
|
# Define crop transforms from cropping_regions
|
|
1197
1312
|
crop_transforms = (TransformStruct * number_of_operations)()
|
|
1313
|
+
# Pre-compute luminance coefficient once for all crops
|
|
1314
|
+
lum_coefficient = None
|
|
1198
1315
|
for i, crop_region in enumerate(crop_regions):
|
|
1199
1316
|
# The fill_background callback is slow, only use it if needed
|
|
1200
1317
|
if self.__need_fill_background(
|
|
@@ -1202,14 +1319,16 @@ class TurboJPEG(object):
|
|
|
1202
1319
|
(image_width, image_height),
|
|
1203
1320
|
background_luminance
|
|
1204
1321
|
):
|
|
1322
|
+
if lum_coefficient is None:
|
|
1323
|
+
lum_coefficient = self.__map_luminance_to_dc_dct_coefficient(
|
|
1324
|
+
bytearray(jpeg_buf),
|
|
1325
|
+
background_luminance
|
|
1326
|
+
)
|
|
1205
1327
|
# Use callback to fill in background post-transform
|
|
1206
1328
|
callback_data = BackgroundStruct(
|
|
1207
1329
|
image_width,
|
|
1208
1330
|
image_height,
|
|
1209
|
-
|
|
1210
|
-
bytearray(jpeg_buf),
|
|
1211
|
-
background_luminance
|
|
1212
|
-
)
|
|
1331
|
+
lum_coefficient
|
|
1213
1332
|
)
|
|
1214
1333
|
callback = CUSTOMFILTER(fill_background)
|
|
1215
1334
|
crop_transforms[i] = TransformStruct(
|
|
@@ -1234,7 +1353,6 @@ class TurboJPEG(object):
|
|
|
1234
1353
|
|
|
1235
1354
|
def buffer_size(self, img_array, jpeg_subsample=TJSAMP_422):
|
|
1236
1355
|
"""Get maximum number of bytes of compressed jpeg data"""
|
|
1237
|
-
img_array = np.ascontiguousarray(img_array)
|
|
1238
1356
|
height, width = img_array.shape[:2]
|
|
1239
1357
|
return self.__buffer_size(width, height, jpeg_subsample)
|
|
1240
1358
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|