A Python wrapper of libjpeg-turbo for decoding and encoding JPEG image.
Project description
PyTurboJPEG
A Python wrapper for libjpeg-turbo that enables efficient JPEG image decoding and encoding.
Prerequisites
- libjpeg-turbo 3.0 or later (required for PyTurboJPEG 2.0+)
- numpy
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.
Installation
macOS
brew install jpeg-turbo
pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
Windows
- Download the libjpeg-turbo official installer
- Install PyTurboJPEG:
pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
Linux
- Download the libjpeg-turbo official installer
- Install PyTurboJPEG:
pip install -U git+https://github.com/lilohuang/PyTurboJPEG.git
Basic Usage
Initialization
from turbojpeg import TurboJPEG
# Use default library installation
jpeg = TurboJPEG()
# Or specify library path explicitly
# jpeg = TurboJPEG(r'D:\turbojpeg.dll') # Windows
# jpeg = TurboJPEG('/usr/lib64/libturbojpeg.so') # Linux
# jpeg = TurboJPEG('/usr/local/lib/libturbojpeg.dylib') # macOS
Decoding
import cv2
from turbojpeg import TurboJPEG, TJPF_GRAY, TJFLAG_FASTUPSAMPLE, TJFLAG_FASTDCT
jpeg = TurboJPEG()
# Basic decoding to BGR array
with open('input.jpg', 'rb') as f:
bgr_array = jpeg.decode(f.read())
cv2.imshow('bgr_array', bgr_array)
cv2.waitKey(0)
# Fast decoding (lower accuracy, higher speed)
with open('input.jpg', 'rb') as f:
bgr_array = jpeg.decode(f.read(), flags=TJFLAG_FASTUPSAMPLE|TJFLAG_FASTDCT)
# Decode with direct rescaling (1/2 size)
with open('input.jpg', 'rb') as f:
bgr_array_half = jpeg.decode(f.read(), scaling_factor=(1, 2))
# Get available scaling factors
scaling_factors = jpeg.scaling_factors
# Decode to grayscale
with open('input.jpg', 'rb') as f:
gray_array = jpeg.decode(f.read(), pixel_format=TJPF_GRAY)
Decoding Header Information
# Get image properties without full decoding (backward compatible)
with open('input.jpg', 'rb') as f:
width, height, jpeg_subsample, jpeg_colorspace = jpeg.decode_header(f.read())
# Get precision to select appropriate decode function
with open('input.jpg', 'rb') as f:
jpeg_data = f.read()
width, height, jpeg_subsample, jpeg_colorspace, precision = jpeg.decode_header(jpeg_data, return_precision=True)
# Use precision to select appropriate decode function
if precision == 8:
img = jpeg.decode(jpeg_data)
elif precision == 12:
img = jpeg.decode_12bit(jpeg_data)
elif precision == 16:
img = jpeg.decode_16bit(jpeg_data)
YUV Decoding
# Decode to YUV buffer
with open('input.jpg', 'rb') as f:
buffer_array, plane_sizes = jpeg.decode_to_yuv(f.read())
# Decode to YUV planes
with open('input.jpg', 'rb') as f:
planes = jpeg.decode_to_yuv_planes(f.read())
Encoding
from turbojpeg import TJSAMP_GRAY, TJFLAG_PROGRESSIVE
# Basic encoding with default settings
with open('output.jpg', 'wb') as f:
f.write(jpeg.encode(bgr_array))
# Encode with grayscale subsample
with open('output_gray.jpg', 'wb') as f:
f.write(jpeg.encode(bgr_array, jpeg_subsample=TJSAMP_GRAY))
# Encode with custom quality
with open('output_quality_50.jpg', 'wb') as f:
f.write(jpeg.encode(bgr_array, quality=50))
# Encode with progressive entropy coding
with open('output_progressive.jpg', 'wb') as f:
f.write(jpeg.encode(bgr_array, quality=100, flags=TJFLAG_PROGRESSIVE))
# Encode with lossless JPEG compression
with open('output_gray.jpg', 'wb') as f:
f.write(jpeg.encode(bgr_array, lossless=True))
Advanced Operations
# Scale with quality (without color conversion)
with open('input.jpg', 'rb') as f:
scaled_data = jpeg.scale_with_quality(f.read(), scaling_factor=(1, 4), quality=70)
with open('scaled_output.jpg', 'wb') as f:
f.write(scaled_data)
# Lossless crop
with open('input.jpg', 'rb') as f:
cropped_data = jpeg.crop(f.read(), 8, 8, 320, 240)
with open('cropped_output.jpg', 'wb') as f:
f.write(cropped_data)
In-Place Operations
import numpy as np
# In-place decoding (reuse existing array)
img_array = np.empty((640, 480, 3), dtype=np.uint8)
with open('input.jpg', 'rb') as f:
result = jpeg.decode(f.read(), dst=img_array)
# result is the same as img_array: id(result) == id(img_array)
# In-place encoding (reuse existing buffer)
buffer_size = jpeg.buffer_size(img_array)
dest_buf = bytearray(buffer_size)
result, n_bytes = jpeg.encode(img_array, dst=dest_buf)
with open('output.jpg', 'wb') as f:
f.write(dest_buf[:n_bytes])
# result is the same as dest_buf: id(result) == id(dest_buf)
EXIF Orientation Handling
import cv2
import numpy as np
import exifread
from turbojpeg import TurboJPEG
def transpose_image(image, orientation):
"""Transpose image based on EXIF Orientation tag.
See: https://www.exif.org/Exif2-2.PDF
"""
if orientation is None:
return image
val = orientation.values[0]
if val == 1: return image
elif val == 2: return np.fliplr(image)
elif val == 3: return np.rot90(image, 2)
elif val == 4: return np.flipud(image)
elif val == 5: return np.rot90(np.flipud(image), -1)
elif val == 6: return np.rot90(image, -1)
elif val == 7: return np.rot90(np.flipud(image))
elif val == 8: return np.rot90(image)
jpeg = TurboJPEG()
with open('foobar.jpg', 'rb') as f:
# Parse EXIF orientation
orientation = exifread.process_file(f).get('Image Orientation', None)
# Decode image
f.seek(0)
image = jpeg.decode(f.read())
# Apply orientation transformation
transposed_image = transpose_image(image, orientation)
cv2.imshow('transposed_image', transposed_image)
cv2.waitKey(0)
ICC Color Management Workflow
import io
import numpy as np
from PIL import Image, ImageCms
from turbojpeg import TurboJPEG, TJPF_BGR
def decode_jpeg_with_color_management(jpeg_path):
"""
Decodes a JPEG and applies color management (ICC Profile to sRGB).
Args:
jpeg_path (str): Path to the input JPEG file.
Returns:
PIL.Image: The color-corrected sRGB Image object.
"""
# 1. Initialize TurboJPEG
jpeg = TurboJPEG()
with open(jpeg_path, 'rb') as f:
jpeg_data = f.read()
# 2. Get image headers and decode pixels
# Using TJPF_BGR format (OpenCV standard) for the raw buffer
width, height, _, _ = jpeg.decode_header(jpeg_data)
pixels = jpeg.decode(jpeg_data, pixel_format=TJPF_BGR)
# 3. Encapsulate into a Pillow Image object
# Key: Use 'raw' and 'BGR' decoder to correctly map BGR bytes to an RGB Image object
img = Image.frombytes('RGB', (width, height), pixels, 'raw', 'BGR')
# 4. Handle ICC Profile transformation
try:
# Extract embedded ICC Profile
icc_profile = jpeg.get_icc_profile(jpeg_data)
if icc_profile:
# Create Source and Destination Profile objects
src_profile = ImageCms.getOpenProfile(io.BytesIO(icc_profile))
dst_profile = ImageCms.createProfile("sRGB")
# Perform color transformation (similar to "Convert to Profile" in Photoshop)
# This step recalculates pixel values to align with sRGB standards
img = ImageCms.profileToProfile(
img,
src_profile,
dst_profile,
outputMode='RGB'
)
print(f"Successfully applied ICC profile from {jpeg_path}")
else:
print("No ICC profile found, assuming sRGB.")
except Exception as e:
print(f"Color Management Error: {e}. Returning original raw image.")
return img
# --- Example Usage ---
if __name__ == "__main__":
result_img = decode_jpeg_with_color_management('icc_profile.jpg')
result_img.show()
# result_img.save('output_srgb.jpg', quality=95)
High-Precision JPEG Support
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.
Requirements:
- libjpeg-turbo 3.0 or later (12-bit and 16-bit support is built-in)
Precision Modes:
- 12-bit JPEG: Supports both lossy and lossless compression
- 16-bit JPEG: Only supports lossless compression (JPEG standard limitation)
12-bit JPEG (Lossy)
12-bit JPEG provides higher precision than standard 8-bit JPEG while maintaining compatibility with lossy compression.
import numpy as np
from turbojpeg import TurboJPEG
jpeg = TurboJPEG()
# Create 12-bit image (values range from 0 to 4095)
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
# Encode to 12-bit lossy JPEG
jpeg_data = jpeg.encode_12bit(img_12bit, quality=95)
# Decode from 12-bit JPEG
decoded_img = jpeg.decode_12bit(jpeg_data)
# Save to file
with open('output_12bit.jpg', 'wb') as f:
f.write(jpeg_data)
# Load from file
with open('output_12bit.jpg', 'rb') as f:
decoded_from_file = jpeg.decode_12bit(f.read())
Lossless JPEG for 12-bit and 16-bit
12-bit and 16-bit JPEG support lossless compression for perfect reconstruction:
12-bit Lossless JPEG
12-bit precision with lossless compression:
import numpy as np
from turbojpeg import TurboJPEG
jpeg = TurboJPEG()
# Create 12-bit image
img_12bit = np.random.randint(0, 4096, (480, 640, 3), dtype=np.uint16)
# Encode to 12-bit lossless JPEG using encode_12bit() with lossless=True
jpeg_data = jpeg.encode_12bit(img_12bit, lossless=True)
# Decode using decode_12bit()
decoded_img = jpeg.decode_12bit(jpeg_data)
# Perfect reconstruction
assert np.array_equal(img_12bit, decoded_img) # True
16-bit Lossless JPEG
16-bit JPEG provides the highest precision with perfect reconstruction through lossless compression. The JPEG standard only supports 16-bit for lossless mode.
import numpy as np
from turbojpeg import TurboJPEG
jpeg = TurboJPEG()
# Create 16-bit image (values range from 0 to 65535)
img_16bit = np.random.randint(0, 65536, (480, 640, 3), dtype=np.uint16)
# Encode to 16-bit lossless JPEG
jpeg_data = jpeg.encode_16bit(img_16bit)
# Decode from 16-bit lossless JPEG
decoded_img = jpeg.decode_16bit(jpeg_data)
# Verify perfect reconstruction (lossless)
assert np.array_equal(img_16bit, decoded_img) # True
# Save to file
with open('output_16bit_lossless.jpg', 'wb') as f:
f.write(jpeg_data)
# Load from file
with open('output_16bit_lossless.jpg', 'rb') as f:
decoded_from_file = jpeg.decode_16bit(f.read())
Medical and Scientific Imaging
For medical and scientific applications, 12-bit JPEG provides excellent precision while maintaining file size efficiency:
import numpy as np
from turbojpeg import TurboJPEG, TJPF_GRAY, TJSAMP_GRAY
jpeg = TurboJPEG()
# Create 12-bit medical image (e.g., DICOM format)
# Medical images typically use 0-4095 range
medical_img = np.random.randint(0, 4096, (512, 512, 1), dtype=np.uint16)
# Encode with highest quality for medical applications
jpeg_medical = jpeg.encode_12bit(
medical_img,
pixel_format=TJPF_GRAY,
jpeg_subsample=TJSAMP_GRAY,
quality=100
)
# Decode for analysis
decoded_medical = jpeg.decode_12bit(jpeg_medical, pixel_format=TJPF_GRAY)
# Verify value range preservation
print(f"Original range: [{medical_img.min()}, {medical_img.max()}]")
print(f"Decoded range: [{decoded_medical.min()}, {decoded_medical.max()}]")
License
See the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file pyturbojpeg-2.2.0.tar.gz.
File metadata
- Download URL: pyturbojpeg-2.2.0.tar.gz
- Upload date:
- Size: 32.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaf0305aa9627ce7fdb8f592eb5e0fce804e1bd87db49900bcf78d7d5138eb88
|
|
| MD5 |
9566e3aff00792b84749979991fb61e1
|
|
| BLAKE2b-256 |
d7c6efd96866c457f22d1e893f9ac67f17ff61a5661ad2a061da61657e9c4999
|