A Python wrapper of libjpeg-turbo for decoding and encoding JPEG images.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Lossless transformation operations for JPEG images including cropping, scaling with quality adjustment, multiple crop operations with background filling, and advanced transform operations that work directly in the compressed domain.
Perform lossless crop operations on JPEG images without decompression/recompression quality loss.
def crop(
jpeg_buf: bytes,
x: int, y: int, w: int, h: int,
preserve: bool = False,
gray: bool = False,
copynone: bool = False
) -> bytes:
"""
Losslessly crop a JPEG image.
Args:
jpeg_buf: Input JPEG data as bytes
x: Crop origin X coordinate (must be MCU-aligned unless preserve=True)
y: Crop origin Y coordinate (must be MCU-aligned unless preserve=True)
w: Crop width in pixels
h: Crop height in pixels
preserve: If True, preserve exact crop boundaries (slower)
gray: Convert to grayscale during crop
copynone: Do not copy EXIF metadata
Returns:
bytes: Cropped JPEG data
Raises:
OSError: If crop request is invalid or coordinates are not MCU-aligned
"""Perform multiple crop and/or extension operations in a single call with optional background filling.
def crop_multiple(
jpeg_buf: bytes,
crop_parameters: list[tuple[int, int, int, int]],
background_luminance: float = 1.0,
gray: bool = False,
copynone: bool = False
) -> list[bytes]:
"""
Lossless crop and/or extension operations on JPEG image.
Args:
jpeg_buf: Input JPEG data as bytes
crop_parameters: List of (x, y, w, h) tuples for each crop operation
background_luminance: Luminance level (0-1) for background fill when extending
gray: Produce grayscale output
copynone: Do not copy EXIF metadata
Returns:
list[bytes]: List of cropped/extended JPEG images
Notes:
- Crop origins must be divisible by MCU block size
- Extensions beyond image boundaries filled with background_luminance color
- background_luminance: 0=black, 0.5=gray, 1=white
"""Scale JPEG images while adjusting quality, combining decompression to YUV, scaling, and recompression in one operation.
def scale_with_quality(
jpeg_buf: bytes,
scaling_factor: tuple[int, int] | None = None,
quality: int = 85,
flags: int = 0
) -> bytes:
"""
Scale JPEG image and re-encode with specified quality.
Args:
jpeg_buf: Input JPEG data as bytes
scaling_factor: Scaling as (numerator, denominator) tuple
quality: Output JPEG quality (1-100)
flags: Processing flags (TJFLAG_* constants)
Returns:
bytes: Scaled and re-encoded JPEG data
Notes:
- Avoids color conversion step for efficiency
- Useful for creating thumbnails with size and quality control
"""TJXOP_NONE: int # No transform
TJXOP_HFLIP: int # Horizontal flip
TJXOP_VFLIP: int # Vertical flip
TJXOP_TRANSPOSE: int # Transpose
TJXOP_TRANSVERSE: int # Transverse
TJXOP_ROT90: int # 90 degree rotation
TJXOP_ROT180: int # 180 degree rotation
TJXOP_ROT270: int # 270 degree rotationTJXOPT_PERFECT: int # Perfect transform (require exact alignment)
TJXOPT_TRIM: int # Trim partial MCUs
TJXOPT_CROP: int # Crop operation
TJXOPT_GRAY: int # Convert to grayscale
TJXOPT_NOOUTPUT: int # No output (validation only)
TJXOPT_PROGRESSIVE: int # Progressive output
TJXOPT_COPYNONE: int # Copy no metadataMCU (Minimum Coded Unit) alignment requirements:
tjMCUWidth: list[int] # MCU width for each subsampling type
tjMCUHeight: list[int] # MCU height for each subsampling type
# MCU sizes by subsampling:
# TJSAMP_444 (4:4:4): 8x8 pixels
# TJSAMP_422 (4:2:2): 16x8 pixels
# TJSAMP_420 (4:2:0): 16x16 pixels
# TJSAMP_GRAY: 8x8 pixels
# TJSAMP_440 (4:4:0): 8x16 pixels
# TJSAMP_411 (4:1:1): 32x8 pixelsclass CroppingRegion(Structure):
"""Defines a rectangular cropping region."""
_fields_ = [
("x", c_int), # X coordinate of crop origin
("y", c_int), # Y coordinate of crop origin
("w", c_int), # Width of crop region
("h", c_int) # Height of crop region
]class ScalingFactor(Structure):
"""Defines a scaling factor as numerator/denominator."""
_fields_ = [
("num", c_int), # Numerator
("denom", c_int) # Denominator
]class BackgroundStruct(Structure):
"""Background fill parameters for image extension operations."""
_fields_ = [
("w", c_int), # Input image width
("h", c_int), # Input image height
("lum", c_int) # Luminance value for background fill
]class TransformStruct(Structure):
"""Complete transform operation definition."""
_fields_ = [
("r", CroppingRegion), # Crop region
("op", c_int), # Transform operation (TJXOP_*)
("options", c_int), # Transform options (TJXOPT_*)
("data", POINTER(BackgroundStruct)), # Background data for extensions
("customFilter", CUSTOMFILTER) # Custom filter function
]from turbojpeg import TurboJPEG
jpeg = TurboJPEG()
# Load JPEG image
with open('input.jpg', 'rb') as f:
jpeg_data = f.read()
# Get image info for crop planning
width, height, subsample, _ = jpeg.decode_header(jpeg_data)
print(f"Original: {width}x{height}, subsampling: {subsample}")
# Crop from (100, 100) with size 300x200
cropped = jpeg.crop(jpeg_data, 100, 100, 300, 200)
# Save cropped image
with open('cropped.jpg', 'wb') as f:
f.write(cropped)# For 4:2:0 subsampling, MCU is 16x16 pixels
# Crop coordinates should be multiples of 16 for perfect alignment
# MCU-aligned crop (fast, lossless)
aligned_crop = jpeg.crop(jpeg_data, 16, 32, 320, 240) # All multiples of 16
# Non-aligned crop with preserve=True (slower but exact)
exact_crop = jpeg.crop(jpeg_data, 15, 33, 318, 238, preserve=True)# Define multiple crop regions
crop_regions = [
(0, 0, 100, 100), # Top-left corner
(100, 0, 100, 100), # Top-right corner
(0, 100, 100, 100), # Bottom-left corner
(100, 100, 100, 100), # Bottom-right corner
]
# Perform all crops in one operation
crops = jpeg.crop_multiple(jpeg_data, crop_regions)
# Save each crop
for i, crop_data in enumerate(crops):
with open(f'crop_{i}.jpg', 'wb') as f:
f.write(crop_data)# Get original image dimensions
width, height, _, _ = jpeg.decode_header(jpeg_data)
# Define crops that extend beyond image boundaries
extended_crops = [
(0, 0, width + 50, height + 50), # Extend right and bottom
(-25, -25, width + 50, height + 50), # Extend all sides
]
# White background fill (luminance = 1.0)
white_bg_crops = jpeg.crop_multiple(
jpeg_data,
extended_crops,
background_luminance=1.0
)
# Gray background fill (luminance = 0.5)
gray_bg_crops = jpeg.crop_multiple(
jpeg_data,
extended_crops,
background_luminance=0.5
)
# Black background fill (luminance = 0.0)
black_bg_crops = jpeg.crop_multiple(
jpeg_data,
extended_crops,
background_luminance=0.0
)# Convert to grayscale during crop
gray_crop = jpeg.crop(
jpeg_data,
100, 100, 200, 200,
gray=True
)
# Multiple grayscale crops
gray_crops = jpeg.crop_multiple(
jpeg_data,
[(0, 0, 100, 100), (100, 100, 100, 100)],
gray=True
)# Scale to half size with quality 70
half_size = jpeg.scale_with_quality(
jpeg_data,
scaling_factor=(1, 2),
quality=70
)
# Scale to quarter size with high quality
quarter_size = jpeg.scale_with_quality(
jpeg_data,
scaling_factor=(1, 4),
quality=90
)
# Available scaling factors
print("Available scaling factors:", jpeg.scaling_factors)
# Common factors: (1,8), (1,4), (3,8), (1,2), (5,8), (3,4), (7,8), (1,1), (9,8), (5,4), (11,8), (3,2), (13,8), (7,4), (15,8), (2,1)# Remove EXIF and other metadata during crop
no_metadata = jpeg.crop(
jpeg_data,
0, 0, width, height, # Full image crop
copynone=True # Strip all metadata
)
# Compare file sizes
print(f"Original size: {len(jpeg_data)} bytes")
print(f"No metadata size: {len(no_metadata)} bytes")# Combine multiple operations
def create_thumbnail_gallery(jpeg_data, thumb_size=(150, 150)):
"""Create multiple thumbnail crops with different qualities."""
# Get image info
width, height, subsample, _ = jpeg.decode_header(jpeg_data)
# Calculate grid positions for thumbnails
thumb_w, thumb_h = thumb_size
cols = width // thumb_w
rows = height // thumb_h
crop_params = []
for row in range(rows):
for col in range(cols):
x = col * thumb_w
y = row * thumb_h
crop_params.append((x, y, thumb_w, thumb_h))
# Extract all thumbnails
thumbnails = jpeg.crop_multiple(jpeg_data, crop_params)
# Scale and adjust quality for web use
web_thumbs = []
for thumb in thumbnails:
web_thumb = jpeg.scale_with_quality(
thumb,
scaling_factor=(1, 2), # Half size
quality=60 # Web quality
)
web_thumbs.append(web_thumb)
return web_thumbs
# Usage
with open('large_image.jpg', 'rb') as f:
image_data = f.read()
thumbnails = create_thumbnail_gallery(image_data)
for i, thumb in enumerate(thumbnails):
with open(f'thumb_{i:03d}.jpg', 'wb') as f:
f.write(thumb)Install with Tessl CLI
npx tessl i tessl/pypi-pyturbo-jpeg