0
# Utility Functions
1
2
Buffer size calculation, scaling factor information, and helper utilities for optimizing memory usage, determining available scaling options, and working with JPEG processing parameters.
3
4
## Capabilities
5
6
### Buffer Size Calculation
7
8
Calculate the maximum buffer size needed for JPEG encoding operations to optimize memory allocation.
9
10
```python { .api }
11
def buffer_size(img_array: np.ndarray, jpeg_subsample: int = TJSAMP_422) -> int:
12
"""
13
Calculate maximum number of bytes of compressed JPEG data.
14
15
Args:
16
img_array: Input image array
17
jpeg_subsample: JPEG subsampling type (TJSAMP_* constants)
18
19
Returns:
20
int: Maximum buffer size needed in bytes
21
22
Notes:
23
- Use this for pre-allocating buffers for in-place encoding
24
- Actual encoded size will typically be smaller
25
- Calculation accounts for worst-case compression scenario
26
"""
27
```
28
29
### Scaling Factor Information
30
31
Access available scaling factors supported by the libjpeg-turbo library for decode operations.
32
33
```python { .api }
34
@property
35
def scaling_factors(self) -> frozenset[tuple[int, int]]:
36
"""
37
Available scaling factors for decode operations.
38
39
Returns:
40
frozenset: Set of (numerator, denominator) tuples representing valid scaling factors
41
42
Notes:
43
- Common factors include (1,8), (1,4), (1,2), (1,1), (2,1) etc.
44
- Use these for efficient decode-time scaling
45
- Scaling happens during JPEG decompression for better performance
46
"""
47
```
48
49
## Constants and Arrays
50
51
### Pixel Size Information
52
53
```python { .api }
54
tjPixelSize: list[int] = [3, 3, 4, 4, 4, 4, 1, 4, 4, 4, 4, 4] # Bytes per pixel for each pixel format
55
# Index corresponds to TJPF_* constants:
56
# TJPF_RGB=0: 3 bytes, TJPF_BGR=1: 3 bytes, TJPF_RGBX=2: 4 bytes,
57
# TJPF_BGRX=3: 4 bytes, TJPF_XBGR=4: 4 bytes, TJPF_XRGB=5: 4 bytes,
58
# TJPF_GRAY=6: 1 byte, TJPF_RGBA=7: 4 bytes, TJPF_BGRA=8: 4 bytes,
59
# TJPF_ABGR=9: 4 bytes, TJPF_ARGB=10: 4 bytes, TJPF_CMYK=11: 4 bytes
60
```
61
62
### MCU Dimensions
63
64
```python { .api }
65
tjMCUWidth: list[int] = [8, 16, 16, 8, 8, 32] # MCU width in pixels for each subsampling type
66
tjMCUHeight: list[int] = [8, 8, 16, 8, 16, 8] # MCU height in pixels for each subsampling type
67
68
# MCU sizes by subsampling index:
69
# TJSAMP_444=0: 8x8, TJSAMP_422=1: 16x8, TJSAMP_420=2: 16x16
70
# TJSAMP_GRAY=3: 8x8, TJSAMP_440=4: 8x16, TJSAMP_411=5: 32x8
71
```
72
73
### Error Constants
74
75
```python { .api }
76
TJERR_WARNING: int = 0 # Warning error level
77
TJERR_FATAL: int = 1 # Fatal error level
78
```
79
80
### MCU Constants
81
82
```python { .api }
83
MCU_WIDTH: int = 8 # Base MCU width in pixels
84
MCU_HEIGHT: int = 8 # Base MCU height in pixels
85
MCU_SIZE: int = 64 # Base MCU size in pixels (8x8)
86
```
87
88
### Platform Library Paths
89
90
```python { .api }
91
DEFAULT_LIB_PATHS: dict[str, list[str]] # Default libjpeg-turbo library paths by platform
92
93
# Platform-specific paths:
94
# 'Darwin': macOS paths (/usr/local/opt/jpeg-turbo/lib/, /opt/homebrew/opt/jpeg-turbo/lib/)
95
# 'Linux': Linux paths (/usr/lib/x86_64-linux-gnu/, /usr/lib64/, etc.)
96
# 'Windows': Windows paths (C:/libjpeg-turbo64/bin/)
97
# 'FreeBSD': FreeBSD paths (/usr/local/lib/)
98
# 'NetBSD': NetBSD paths (/usr/pkg/lib/)
99
```
100
101
## Utility Functions
102
103
### Nibble Operations
104
105
```python { .api }
106
def split_byte_into_nibbles(value: int) -> tuple[int, int]:
107
"""
108
Split byte into two 4-bit nibbles.
109
110
Args:
111
value: Byte value (0-255)
112
113
Returns:
114
tuple[int, int]: (first_nibble, second_nibble) where each is 0-15
115
"""
116
```
117
118
## Usage Examples
119
120
### Buffer Size Calculation
121
122
```python
123
import numpy as np
124
from turbojpeg import TurboJPEG, TJSAMP_420, TJSAMP_444
125
126
jpeg = TurboJPEG()
127
128
# Create sample image
129
image = np.random.randint(0, 256, (1080, 1920, 3), dtype=np.uint8)
130
131
# Calculate buffer sizes for different subsampling
132
size_422 = jpeg.buffer_size(image) # Default TJSAMP_422
133
size_420 = jpeg.buffer_size(image, TJSAMP_420)
134
size_444 = jpeg.buffer_size(image, TJSAMP_444)
135
136
print(f"Image shape: {image.shape}")
137
print(f"Buffer size 4:2:2: {size_422:,} bytes")
138
print(f"Buffer size 4:2:0: {size_420:,} bytes")
139
print(f"Buffer size 4:4:4: {size_444:,} bytes")
140
141
# Pre-allocate buffer for in-place encoding
142
buffer = bytearray(size_422)
143
encoded_data, actual_size = jpeg.encode(image, dst=buffer)
144
145
print(f"Actual encoded size: {actual_size:,} bytes")
146
print(f"Buffer utilization: {actual_size/size_422*100:.1f}%")
147
```
148
149
### Scaling Factor Usage
150
151
```python
152
# Check all available scaling factors
153
print("Available scaling factors:")
154
for num, denom in sorted(jpeg.scaling_factors):
155
scale = num / denom
156
print(f" {num}/{denom} = {scale:.3f}x")
157
158
# Common scaling factors and their uses
159
common_scales = {
160
(1, 8): "1/8 scale - tiny thumbnails",
161
(1, 4): "1/4 scale - small thumbnails",
162
(1, 2): "1/2 scale - medium thumbnails",
163
(1, 1): "1:1 scale - original size",
164
(2, 1): "2x scale - upscaling"
165
}
166
167
print("\nCommon uses:")
168
for factor, description in common_scales.items():
169
if factor in jpeg.scaling_factors:
170
print(f" {factor[0]}/{factor[1]}: {description}")
171
```
172
173
### Efficient Thumbnail Generation
174
175
```python
176
def generate_thumbnails(jpeg_data, sizes):
177
"""Generate multiple thumbnail sizes efficiently."""
178
179
jpeg = TurboJPEG()
180
thumbnails = {}
181
182
# Get available scaling factors
183
available_factors = jpeg.scaling_factors
184
185
for name, target_size in sizes.items():
186
# Find best scaling factor
187
best_factor = None
188
best_scale = float('inf')
189
190
for num, denom in available_factors:
191
scale = num / denom
192
if 0.1 <= scale <= 1.0: # Only downscaling factors
193
if abs(scale - target_size) < abs(best_scale - target_size):
194
best_factor = (num, denom)
195
best_scale = scale
196
197
if best_factor:
198
# Decode at reduced scale
199
thumbnail = jpeg.decode(
200
jpeg_data,
201
scaling_factor=best_factor
202
)
203
thumbnails[name] = thumbnail
204
print(f"{name}: using {best_factor[0]}/{best_factor[1]} = {best_scale:.3f}x")
205
else:
206
# Fallback to full decode + resize
207
print(f"{name}: no suitable scaling factor, using full decode")
208
209
return thumbnails
210
211
# Usage
212
with open('photo.jpg', 'rb') as f:
213
photo_data = f.read()
214
215
thumbnail_sizes = {
216
'tiny': 0.125, # 1/8 scale
217
'small': 0.25, # 1/4 scale
218
'medium': 0.5, # 1/2 scale
219
}
220
221
thumbs = generate_thumbnails(photo_data, thumbnail_sizes)
222
```
223
224
### Memory Optimization
225
226
```python
227
def encode_with_optimal_buffer(image_array, quality=85):
228
"""Encode with pre-calculated optimal buffer size."""
229
230
jpeg = TurboJPEG()
231
232
# Calculate exact buffer size needed
233
max_size = jpeg.buffer_size(image_array)
234
235
# Allocate buffer
236
buffer = bytearray(max_size)
237
238
# Encode in-place
239
result_buffer, actual_size = jpeg.encode(
240
image_array,
241
quality=quality,
242
dst=buffer
243
)
244
245
# Return only the used portion
246
return bytes(buffer[:actual_size])
247
248
# Compare with standard encoding
249
image = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
250
251
# Standard encoding (allocates internally)
252
standard_result = jpeg.encode(image)
253
254
# Optimized encoding (pre-allocated buffer)
255
optimal_result = encode_with_optimal_buffer(image)
256
257
print(f"Standard result size: {len(standard_result)} bytes")
258
print(f"Optimal result size: {len(optimal_result)} bytes")
259
print(f"Results identical: {standard_result == optimal_result}")
260
```
261
262
### Platform-Specific Library Loading
263
264
```python
265
import platform
266
from turbojpeg import TurboJPEG, DEFAULT_LIB_PATHS
267
268
def create_jpeg_processor():
269
"""Create TurboJPEG processor with platform-specific fallbacks."""
270
271
# Try default initialization first
272
try:
273
return TurboJPEG()
274
except RuntimeError as e:
275
print(f"Default initialization failed: {e}")
276
277
# Try platform-specific paths
278
system = platform.system()
279
if system in DEFAULT_LIB_PATHS:
280
for lib_path in DEFAULT_LIB_PATHS[system]:
281
try:
282
print(f"Trying {lib_path}...")
283
return TurboJPEG(lib_path=lib_path)
284
except (RuntimeError, OSError):
285
continue
286
287
# Final fallback - let user specify
288
raise RuntimeError(
289
"Could not locate libjpeg-turbo library. "
290
"Please install libjpeg-turbo or specify lib_path manually."
291
)
292
293
# Usage
294
try:
295
jpeg = create_jpeg_processor()
296
print("TurboJPEG initialized successfully")
297
print(f"Available scaling factors: {len(jpeg.scaling_factors)}")
298
except RuntimeError as e:
299
print(f"Failed to initialize: {e}")
300
```
301
302
### Working with MCU Alignment
303
304
```python
305
def get_mcu_aligned_crop(width, height, subsample, x, y, w, h):
306
"""Calculate MCU-aligned crop coordinates."""
307
308
from turbojpeg import tjMCUWidth, tjMCUHeight
309
310
mcu_w = tjMCUWidth[subsample]
311
mcu_h = tjMCUHeight[subsample]
312
313
# Align to MCU boundaries
314
aligned_x = (x // mcu_w) * mcu_w
315
aligned_y = (y // mcu_h) * mcu_h
316
aligned_w = ((w + mcu_w - 1) // mcu_w) * mcu_w
317
aligned_h = ((h + mcu_h - 1) // mcu_h) * mcu_h
318
319
# Ensure within image bounds
320
aligned_w = min(aligned_w, width - aligned_x)
321
aligned_h = min(aligned_h, height - aligned_y)
322
323
return aligned_x, aligned_y, aligned_w, aligned_h
324
325
# Usage
326
jpeg = TurboJPEG()
327
328
with open('image.jpg', 'rb') as f:
329
jpeg_data = f.read()
330
331
# Get image properties
332
width, height, subsample, _ = jpeg.decode_header(jpeg_data)
333
334
# Desired crop
335
desired_crop = (100, 150, 300, 200)
336
337
# Get MCU-aligned version
338
aligned_crop = get_mcu_aligned_crop(
339
width, height, subsample, *desired_crop
340
)
341
342
print(f"Desired crop: {desired_crop}")
343
print(f"MCU-aligned crop: {aligned_crop}")
344
print(f"Subsampling: {subsample} (MCU: {tjMCUWidth[subsample]}x{tjMCUHeight[subsample]})")
345
346
# Perform aligned crop (fast)
347
cropped = jpeg.crop(jpeg_data, *aligned_crop)
348
```