PyTurboJPEG 2.0.0__tar.gz → 2.2.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.
@@ -0,0 +1,443 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyTurboJPEG
3
+ Version: 2.2.0
4
+ Summary: A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image.
5
+ Home-page: https://github.com/lilohuang/PyTurboJPEG
6
+ Author: Lilo Huang
7
+ Author-email: kuso.cc@gmail.com
8
+ License: MIT
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: numpy
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest>=7.0.0; extra == "test"
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: license
20
+ Dynamic: license-file
21
+ Dynamic: provides-extra
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
24
+
25
+ # PyTurboJPEG
26
+
27
+ A Python wrapper for libjpeg-turbo that enables efficient JPEG image decoding and encoding.
28
+
29
+ [![PyPI Version](https://img.shields.io/pypi/v/pyturbojpeg.svg?style=flat-square&color=blue)](https://pypi.org/project/pyturbojpeg/)
30
+ ![Python Version](https://img.shields.io/badge/python-3.6+-blue?logo=python&logoColor=white)
31
+ [![Downloads](https://img.shields.io/pypi/dm/pyturbojpeg.svg?style=flat-square&color=orange)](https://pypistats.org/packages/pyturbojpeg)
32
+ [![License](https://img.shields.io/github/license/lilohuang/PyTurboJPEG.svg?style=flat-square)](https://github.com/lilohuang/PyTurboJPEG/blob/master/LICENSE)
33
+
34
+ ## Prerequisites
35
+
36
+ - [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo/releases) **3.0 or later** (required for PyTurboJPEG 2.0+)
37
+ - [numpy](https://github.com/numpy/numpy)
38
+
39
+ **Important:** PyTurboJPEG 2.0+ requires libjpeg-turbo 3.0 or later as it uses the new function-based TurboJPEG 3 API. For libjpeg-turbo 2.x compatibility, please use PyTurboJPEG 1.x.
40
+
41
+ ## Installation
42
+
43
+ ### macOS
44
+ ```bash
45
+ brew install jpeg-turbo
46
+ pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
47
+ ```
48
+
49
+ ### Windows
50
+ 1. Download the [libjpeg-turbo official installer](https://github.com/libjpeg-turbo/libjpeg-turbo/releases)
51
+ 2. Install PyTurboJPEG:
52
+ ```bash
53
+ pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
54
+ ```
55
+
56
+ ### Linux
57
+ 1. Download the [libjpeg-turbo official installer](https://github.com/libjpeg-turbo/libjpeg-turbo/releases)
58
+ 2. Install PyTurboJPEG:
59
+ ```bash
60
+ pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
61
+ ```
62
+
63
+ ## Basic Usage
64
+
65
+ ### Initialization
66
+
67
+ ```python
68
+ from turbojpeg import TurboJPEG
69
+
70
+ # Use default library installation
71
+ jpeg = TurboJPEG()
72
+
73
+ # Or specify library path explicitly
74
+ # jpeg = TurboJPEG(r'D:\turbojpeg.dll') # Windows
75
+ # jpeg = TurboJPEG('/usr/lib64/libturbojpeg.so') # Linux
76
+ # jpeg = TurboJPEG('/usr/local/lib/libturbojpeg.dylib') # macOS
77
+ ```
78
+
79
+ ### Decoding
80
+
81
+ ```python
82
+ import cv2
83
+ from turbojpeg import TurboJPEG, TJPF_GRAY, TJFLAG_FASTUPSAMPLE, TJFLAG_FASTDCT
84
+
85
+ jpeg = TurboJPEG()
86
+
87
+ # Basic decoding to BGR array
88
+ with open('input.jpg', 'rb') as f:
89
+ bgr_array = jpeg.decode(f.read())
90
+ cv2.imshow('bgr_array', bgr_array)
91
+ cv2.waitKey(0)
92
+
93
+ # Fast decoding (lower accuracy, higher speed)
94
+ with open('input.jpg', 'rb') as f:
95
+ bgr_array = jpeg.decode(f.read(), flags=TJFLAG_FASTUPSAMPLE|TJFLAG_FASTDCT)
96
+
97
+ # Decode with direct rescaling (1/2 size)
98
+ with open('input.jpg', 'rb') as f:
99
+ bgr_array_half = jpeg.decode(f.read(), scaling_factor=(1, 2))
100
+
101
+ # Get available scaling factors
102
+ scaling_factors = jpeg.scaling_factors
103
+
104
+ # Decode to grayscale
105
+ with open('input.jpg', 'rb') as f:
106
+ gray_array = jpeg.decode(f.read(), pixel_format=TJPF_GRAY)
107
+ ```
108
+
109
+ ### Decoding Header Information
110
+
111
+ ```python
112
+ # Get image properties without full decoding (backward compatible)
113
+ with open('input.jpg', 'rb') as f:
114
+ width, height, jpeg_subsample, jpeg_colorspace = jpeg.decode_header(f.read())
115
+
116
+ # Get precision to select appropriate decode function
117
+ with open('input.jpg', 'rb') as f:
118
+ jpeg_data = f.read()
119
+ width, height, jpeg_subsample, jpeg_colorspace, precision = jpeg.decode_header(jpeg_data, return_precision=True)
120
+
121
+ # Use precision to select appropriate decode function
122
+ if precision == 8:
123
+ img = jpeg.decode(jpeg_data)
124
+ elif precision == 12:
125
+ img = jpeg.decode_12bit(jpeg_data)
126
+ elif precision == 16:
127
+ img = jpeg.decode_16bit(jpeg_data)
128
+ ```
129
+
130
+ ### YUV Decoding
131
+
132
+ ```python
133
+ # Decode to YUV buffer
134
+ with open('input.jpg', 'rb') as f:
135
+ buffer_array, plane_sizes = jpeg.decode_to_yuv(f.read())
136
+
137
+ # Decode to YUV planes
138
+ with open('input.jpg', 'rb') as f:
139
+ planes = jpeg.decode_to_yuv_planes(f.read())
140
+ ```
141
+
142
+ ### Encoding
143
+
144
+ ```python
145
+ from turbojpeg import TJSAMP_GRAY, TJFLAG_PROGRESSIVE
146
+
147
+ # Basic encoding with default settings
148
+ with open('output.jpg', 'wb') as f:
149
+ f.write(jpeg.encode(bgr_array))
150
+
151
+ # Encode with grayscale subsample
152
+ with open('output_gray.jpg', 'wb') as f:
153
+ f.write(jpeg.encode(bgr_array, jpeg_subsample=TJSAMP_GRAY))
154
+
155
+ # Encode with custom quality
156
+ with open('output_quality_50.jpg', 'wb') as f:
157
+ f.write(jpeg.encode(bgr_array, quality=50))
158
+
159
+ # Encode with progressive entropy coding
160
+ with open('output_progressive.jpg', 'wb') as f:
161
+ f.write(jpeg.encode(bgr_array, quality=100, flags=TJFLAG_PROGRESSIVE))
162
+
163
+ # Encode with lossless JPEG compression
164
+ with open('output_gray.jpg', 'wb') as f:
165
+ f.write(jpeg.encode(bgr_array, lossless=True))
166
+ ```
167
+
168
+ ### Advanced Operations
169
+
170
+ ```python
171
+ # Scale with quality (without color conversion)
172
+ with open('input.jpg', 'rb') as f:
173
+ scaled_data = jpeg.scale_with_quality(f.read(), scaling_factor=(1, 4), quality=70)
174
+ with open('scaled_output.jpg', 'wb') as f:
175
+ f.write(scaled_data)
176
+
177
+ # Lossless crop
178
+ with open('input.jpg', 'rb') as f:
179
+ cropped_data = jpeg.crop(f.read(), 8, 8, 320, 240)
180
+ with open('cropped_output.jpg', 'wb') as f:
181
+ f.write(cropped_data)
182
+ ```
183
+
184
+ ### In-Place Operations
185
+
186
+ ```python
187
+ import numpy as np
188
+
189
+ # In-place decoding (reuse existing array)
190
+ img_array = np.empty((640, 480, 3), dtype=np.uint8)
191
+ with open('input.jpg', 'rb') as f:
192
+ result = jpeg.decode(f.read(), dst=img_array)
193
+ # result is the same as img_array: id(result) == id(img_array)
194
+
195
+ # In-place encoding (reuse existing buffer)
196
+ buffer_size = jpeg.buffer_size(img_array)
197
+ dest_buf = bytearray(buffer_size)
198
+ result, n_bytes = jpeg.encode(img_array, dst=dest_buf)
199
+ with open('output.jpg', 'wb') as f:
200
+ f.write(dest_buf[:n_bytes])
201
+ # result is the same as dest_buf: id(result) == id(dest_buf)
202
+ ```
203
+
204
+ ### EXIF Orientation Handling
205
+
206
+ ```python
207
+ import cv2
208
+ import numpy as np
209
+ import exifread
210
+ from turbojpeg import TurboJPEG
211
+
212
+ def transpose_image(image, orientation):
213
+ """Transpose image based on EXIF Orientation tag.
214
+
215
+ See: https://www.exif.org/Exif2-2.PDF
216
+ """
217
+ if orientation is None:
218
+ return image
219
+
220
+ val = orientation.values[0]
221
+ if val == 1: return image
222
+ elif val == 2: return np.fliplr(image)
223
+ elif val == 3: return np.rot90(image, 2)
224
+ elif val == 4: return np.flipud(image)
225
+ elif val == 5: return np.rot90(np.flipud(image), -1)
226
+ elif val == 6: return np.rot90(image, -1)
227
+ elif val == 7: return np.rot90(np.flipud(image))
228
+ elif val == 8: return np.rot90(image)
229
+
230
+ jpeg = TurboJPEG()
231
+
232
+ with open('foobar.jpg', 'rb') as f:
233
+ # Parse EXIF orientation
234
+ orientation = exifread.process_file(f).get('Image Orientation', None)
235
+
236
+ # Decode image
237
+ f.seek(0)
238
+ image = jpeg.decode(f.read())
239
+
240
+ # Apply orientation transformation
241
+ transposed_image = transpose_image(image, orientation)
242
+
243
+ cv2.imshow('transposed_image', transposed_image)
244
+ cv2.waitKey(0)
245
+ ```
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
+
314
+ ## High-Precision JPEG Support
315
+
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.
317
+
318
+ **Requirements:**
319
+ - libjpeg-turbo 3.0 or later (12-bit and 16-bit support is built-in)
320
+
321
+ **Precision Modes:**
322
+ - **12-bit JPEG:** Supports both lossy and lossless compression
323
+ - **16-bit JPEG:** Only supports lossless compression (JPEG standard limitation)
324
+
325
+ ### 12-bit JPEG (Lossy)
326
+
327
+ 12-bit JPEG provides higher precision than standard 8-bit JPEG while maintaining compatibility with lossy compression.
328
+
329
+ ```python
330
+ import numpy as np
331
+ from turbojpeg import TurboJPEG
332
+
333
+ jpeg = TurboJPEG()
334
+
335
+ # Create 12-bit image (values range from 0 to 4095)
336
+ img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
337
+
338
+ # Encode to 12-bit lossy JPEG
339
+ jpeg_data = jpeg.encode_12bit(img_12bit, quality=95)
340
+
341
+ # Decode from 12-bit JPEG
342
+ decoded_img = jpeg.decode_12bit(jpeg_data)
343
+
344
+ # Save to file
345
+ with open('output_12bit.jpg', 'wb') as f:
346
+ f.write(jpeg_data)
347
+
348
+ # Load from file
349
+ with open('output_12bit.jpg', 'rb') as f:
350
+ decoded_from_file = jpeg.decode_12bit(f.read())
351
+ ```
352
+
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
381
+
382
+ 16-bit JPEG provides the highest precision with perfect reconstruction through lossless compression. The JPEG standard only supports 16-bit for lossless mode.
383
+
384
+ ```python
385
+ import numpy as np
386
+ from turbojpeg import TurboJPEG
387
+
388
+ jpeg = TurboJPEG()
389
+
390
+ # Create 16-bit image (values range from 0 to 65535)
391
+ img_16bit = np.random.randint(0, 65536, (480, 640, 3), dtype=np.uint16)
392
+
393
+ # Encode to 16-bit lossless JPEG
394
+ jpeg_data = jpeg.encode_16bit(img_16bit)
395
+
396
+ # Decode from 16-bit lossless JPEG
397
+ decoded_img = jpeg.decode_16bit(jpeg_data)
398
+
399
+ # Verify perfect reconstruction (lossless)
400
+ assert np.array_equal(img_16bit, decoded_img) # True
401
+
402
+ # Save to file
403
+ with open('output_16bit_lossless.jpg', 'wb') as f:
404
+ f.write(jpeg_data)
405
+
406
+ # Load from file
407
+ with open('output_16bit_lossless.jpg', 'rb') as f:
408
+ decoded_from_file = jpeg.decode_16bit(f.read())
409
+ ```
410
+
411
+ ### Medical and Scientific Imaging
412
+
413
+ For medical and scientific applications, 12-bit JPEG provides excellent precision while maintaining file size efficiency:
414
+
415
+ ```python
416
+ import numpy as np
417
+ from turbojpeg import TurboJPEG, TJPF_GRAY, TJSAMP_GRAY
418
+
419
+ jpeg = TurboJPEG()
420
+
421
+ # Create 12-bit medical image (e.g., DICOM format)
422
+ # Medical images typically use 0-4095 range
423
+ medical_img = np.random.randint(0, 4096, (512, 512, 1), dtype=np.uint16)
424
+
425
+ # Encode with highest quality for medical applications
426
+ jpeg_medical = jpeg.encode_12bit(
427
+ medical_img,
428
+ pixel_format=TJPF_GRAY,
429
+ jpeg_subsample=TJSAMP_GRAY,
430
+ quality=100
431
+ )
432
+
433
+ # Decode for analysis
434
+ decoded_medical = jpeg.decode_12bit(jpeg_medical, pixel_format=TJPF_GRAY)
435
+
436
+ # Verify value range preservation
437
+ print(f"Original range: [{medical_img.min()}, {medical_img.max()}]")
438
+ print(f"Decoded range: [{decoded_medical.min()}, {decoded_medical.max()}]")
439
+ ```
440
+
441
+ ## License
442
+
443
+ See the LICENSE file for details.