pure_jpeg 0.3.3 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +10 -8
- data/lib/pure_jpeg/decoder.rb +20 -25
- data/lib/pure_jpeg/encoder.rb +55 -56
- data/lib/pure_jpeg/huffman/decoder.rb +4 -2
- data/lib/pure_jpeg/huffman/encoder.rb +4 -10
- data/lib/pure_jpeg/quantization.rb +28 -0
- data/lib/pure_jpeg/source/raw_source.rb +3 -2
- data/lib/pure_jpeg/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 81f59b74746cbb7c575aa22e250667b8b126a542613833f2e45b57685fed02c0
|
|
4
|
+
data.tar.gz: 87fb8c2b6dd5035d93c4326a37fac8e43ade2af88f92a3dd1f7761960f675126
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 930f8db2b6ab86080c115e77f56e0918853e455dbcd18129daa5aaca77715df198a502010ae218275799e8b364f115232e11139ffcb42ca981c57478ef12b45b
|
|
7
|
+
data.tar.gz: 54c50b3a2316c6fa0b9db38fcabdd589bcc17d2edfeff653a28ec4054a72c2b5615eef5fd9f123fb31ce56448864495f49942bf63c4536d00c21fd4e7a6eb4f2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
Performance:
|
|
6
|
+
|
|
7
|
+
- Faster Huffman coding and decoding, with scan references resolved once per scan
|
|
8
|
+
- Combined quantization and zigzag passes, including direct reconstruction from progressive coefficient buffers
|
|
9
|
+
- Color encoding downsamples chroma two rows at a time instead of allocating full-resolution chroma images
|
|
10
|
+
- Block-based `RawSource` construction avoids allocating pixels that would immediately be replaced
|
|
11
|
+
- Measured encode/decode times reduced by roughly 12–28% on Ruby 4.0.2 with YJIT; baseline decode allocations fell from 76,994 to 114 on the bundled 1024x1024 benchmark
|
|
12
|
+
- Encoding array storage allocated for that image fell from 29.4 MB to 12.6 MB, with encoded bytes and decoded pixels preserved in equivalence checks
|
|
13
|
+
|
|
14
|
+
Compatibility and testing:
|
|
15
|
+
|
|
16
|
+
- Experimental Spinel compatibility, tested at revision `7f08abd9`, with benchmarks and a `rake test:spinel` proof-of-concept runner
|
|
17
|
+
- Spinel currently passes seven of the eight selected RawSource tests; the remaining test fails because `respond_to?` is unavailable on its pixel struct. See `test/SPINEL.md` for details
|
|
18
|
+
- Added regression tests for quantization rounding, progressive coefficient offsets, and chroma downsampling edges
|
|
19
|
+
- Updated performance comparisons for CRuby, TruffleRuby, JRuby, and Spinel
|
|
20
|
+
|
|
3
21
|
## 0.3.3
|
|
4
22
|
|
|
5
23
|
New features:
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Convert PNG or other pixel data to JPEG. Or the other way! Implements baseline JPEG encoding (DCT, Huffman, 4:2:0 chroma subsampling) and decodes both baseline and progressive JPEGs. Exposes a variety of encoding options to adjust parts of the JPEG pipeline not normally available (I needed this to recreate the JPEG compression styles of older digital cameras - don't ask..)
|
|
8
8
|
|
|
9
|
-
It works on CRuby 3.0+, TruffleRuby 33.0,
|
|
9
|
+
It works on CRuby 3.0+, TruffleRuby 33.0, JRuby 10.0, and [Spinel](https://github.com/matz/spinel) (tested at `7f08abd9`). There's *almost* 100% test coverage - I need to find some "broken" JPEGs to do the rest (hit me up if you have any sources..)
|
|
10
10
|
|
|
11
11
|
> [!NOTE]
|
|
12
12
|
> Rubyists might find the [AI Disclosure](#ai-disclosure) section below of interest.
|
|
@@ -205,15 +205,17 @@ Possible future improvements: ICC profile rendering/conversion.
|
|
|
205
205
|
|
|
206
206
|
## Performance
|
|
207
207
|
|
|
208
|
-
On
|
|
208
|
+
On the bundled 1024x1024 images (Apple M5, median of 15 runs after 20 seconds of warmup per operation):
|
|
209
209
|
|
|
210
|
-
| Operation | CRuby 4.0.2 (YJIT) | TruffleRuby
|
|
211
|
-
|
|
212
|
-
| Encode (color, q85) | ~0.
|
|
213
|
-
| Decode (baseline) | ~0.
|
|
214
|
-
| Decode (progressive) | ~0.
|
|
210
|
+
| Operation | CRuby 4.0.2 (YJIT) | TruffleRuby 34.0.1 | JRuby 9.4.14.0 | Spinel (`7f08abd9`) |
|
|
211
|
+
|-----------|-------------------|-------------------|---------------|--------------------|
|
|
212
|
+
| Encode (color, q85) | ~0.131s | ~0.088s | ~0.577s | ~0.104s |
|
|
213
|
+
| Decode (baseline) | ~0.112s | ~0.027s | ~0.554s | ~0.078s |
|
|
214
|
+
| Decode (progressive) | ~0.140s | ~0.037s | ~0.617s | ~0.092s |
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
Inputs were loaded before timing, and encoded output was returned in memory; file I/O and PNG loading are excluded. Runtimes were benchmarked sequentially. JRuby used its default JIT on OpenJDK 21.0.10. Spinel was measured separately with the same inputs and timing protocol, using a portable harness with PNG pixels preloaded; its encoded JPEG and decoded pixels matched CRuby byte-for-byte. Results vary with image content, garbage collection, and JIT warmup.
|
|
217
|
+
|
|
218
|
+
The encoder and decoder use an integer-scaled AAN (Arai-Agui-Nakajima) DCT with fixed-point arithmetic throughout — no Float operations in the hot path. Color space conversion uses fixed-point integer math, and pixel data is stored as packed integers to avoid per-pixel object allocation. TruffleRuby's Graal JIT compiler can optimize these tight integer loops particularly well once warmed up.
|
|
217
219
|
|
|
218
220
|
## Example scripts
|
|
219
221
|
|
data/lib/pure_jpeg/decoder.rb
CHANGED
|
@@ -78,9 +78,13 @@ module PureJPEG
|
|
|
78
78
|
|
|
79
79
|
# Reusable buffers
|
|
80
80
|
zigzag = Array.new(64, 0)
|
|
81
|
-
raster = Array.new(64, 0)
|
|
82
81
|
dequant = Array.new(64, 0)
|
|
83
82
|
|
|
83
|
+
scan_components = jfif.scan_components.map do |sc|
|
|
84
|
+
comp, dc_tab, ac_tab = resolve_scan_references!(sc, comp_info, dc_tables, ac_tables)
|
|
85
|
+
[sc, comp, dc_tab, ac_tab, fetch_quant_table!(jfif, comp), channels[comp.id]]
|
|
86
|
+
end
|
|
87
|
+
|
|
84
88
|
mcus_y.times do |mcu_row|
|
|
85
89
|
mcus_x.times do |mcu_col|
|
|
86
90
|
# Handle restart interval
|
|
@@ -89,19 +93,14 @@ module PureJPEG
|
|
|
89
93
|
prev_dc.clear
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
comp, dc_tab, ac_tab = resolve_scan_references!(sc, comp_info, dc_tables, ac_tables)
|
|
94
|
-
qt = fetch_quant_table!(jfif, comp)
|
|
95
|
-
ch = channels[comp.id]
|
|
96
|
-
|
|
96
|
+
scan_components.each do |sc, comp, dc_tab, ac_tab, qt, ch|
|
|
97
97
|
comp.v_sampling.times do |bv|
|
|
98
98
|
comp.h_sampling.times do |bh|
|
|
99
99
|
# Decode one 8x8 block
|
|
100
100
|
decode_block(reader, dc_tab, ac_tab, prev_dc, sc.id, zigzag)
|
|
101
101
|
|
|
102
102
|
# Inverse pipeline: unzigzag -> dequantize -> IDCT -> level shift
|
|
103
|
-
|
|
104
|
-
Quantization.dequantize!(raster, qt, dequant)
|
|
103
|
+
Quantization.dequantize_zigzag!(zigzag, qt, dequant)
|
|
105
104
|
DCT.inverse!(dequant)
|
|
106
105
|
|
|
107
106
|
# Write block into channel buffer
|
|
@@ -191,7 +190,7 @@ module PureJPEG
|
|
|
191
190
|
end
|
|
192
191
|
end
|
|
193
192
|
|
|
194
|
-
# Reconstruct: unzigzag
|
|
193
|
+
# Reconstruct: unzigzag/dequantize, IDCT, write to channel buffers
|
|
195
194
|
padded_w = mcus_x * mcu_px_w
|
|
196
195
|
padded_h = mcus_y * mcu_px_h
|
|
197
196
|
channels = {}
|
|
@@ -201,8 +200,6 @@ module PureJPEG
|
|
|
201
200
|
channels[c.id] = { data: Array.new(ch_w * ch_h, 0), width: ch_w, height: ch_h }
|
|
202
201
|
end
|
|
203
202
|
|
|
204
|
-
zigzag = Array.new(64, 0)
|
|
205
|
-
raster = Array.new(64, 0)
|
|
206
203
|
dequant = Array.new(64, 0)
|
|
207
204
|
|
|
208
205
|
jfif.components.each do |c|
|
|
@@ -214,10 +211,7 @@ module PureJPEG
|
|
|
214
211
|
by_count.times do |block_y|
|
|
215
212
|
bx_count.times do |block_x|
|
|
216
213
|
offset = (block_y * bx_count + block_x) * 64
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Zigzag.unreorder!(zigzag, raster)
|
|
220
|
-
Quantization.dequantize!(raster, qt, dequant)
|
|
214
|
+
Quantization.dequantize_zigzag!(coeff_buf, qt, dequant, offset)
|
|
221
215
|
DCT.inverse!(dequant)
|
|
222
216
|
write_block(dequant, ch[:data], ch[:width], block_x * 8, block_y * 8)
|
|
223
217
|
end
|
|
@@ -279,6 +273,11 @@ module PureJPEG
|
|
|
279
273
|
prev_dc = Hash.new(0)
|
|
280
274
|
mcu_count = 0
|
|
281
275
|
|
|
276
|
+
scan_components = scan.components.map do |sc|
|
|
277
|
+
comp, dc_tab = resolve_scan_references!(sc, comp_info, dc_tables, ac_tables, require_ac: false)
|
|
278
|
+
[sc, comp, dc_tab, coeffs[comp.id], comp_blocks[comp.id][0]]
|
|
279
|
+
end
|
|
280
|
+
|
|
282
281
|
mcus_y.times do |mcu_row|
|
|
283
282
|
mcus_x.times do |mcu_col|
|
|
284
283
|
if restart_interval > 0 && mcu_count > 0 && (mcu_count % restart_interval) == 0
|
|
@@ -286,11 +285,7 @@ module PureJPEG
|
|
|
286
285
|
prev_dc.clear
|
|
287
286
|
end
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
comp, dc_tab = resolve_scan_references!(sc, comp_info, dc_tables, ac_tables, require_ac: false)
|
|
291
|
-
coeff_buf = coeffs[comp.id]
|
|
292
|
-
bx_count = comp_blocks[comp.id][0]
|
|
293
|
-
|
|
288
|
+
scan_components.each do |sc, comp, dc_tab, coeff_buf, bx_count|
|
|
294
289
|
comp.v_sampling.times do |bv|
|
|
295
290
|
comp.h_sampling.times do |bh|
|
|
296
291
|
block_x = mcu_col * comp.h_sampling + bh
|
|
@@ -312,7 +307,7 @@ module PureJPEG
|
|
|
312
307
|
end
|
|
313
308
|
|
|
314
309
|
def prog_dc_first(reader, dc_tab, prev_dc, coeff_buf, offset, al)
|
|
315
|
-
cat = dc_tab.
|
|
310
|
+
cat = dc_tab.decode_symbol(reader)
|
|
316
311
|
diff = reader.receive_extend(cat)
|
|
317
312
|
dc_val = prev_dc + diff
|
|
318
313
|
coeff_buf[offset] = dc_val << al
|
|
@@ -328,7 +323,7 @@ module PureJPEG
|
|
|
328
323
|
|
|
329
324
|
k = ss
|
|
330
325
|
while k <= se
|
|
331
|
-
symbol = ac_tab.
|
|
326
|
+
symbol = ac_tab.decode_symbol(reader)
|
|
332
327
|
run = (symbol >> 4) & 0x0F
|
|
333
328
|
size = symbol & 0x0F
|
|
334
329
|
|
|
@@ -364,7 +359,7 @@ module PureJPEG
|
|
|
364
359
|
|
|
365
360
|
k = ss
|
|
366
361
|
while k <= se
|
|
367
|
-
symbol = ac_tab.
|
|
362
|
+
symbol = ac_tab.decode_symbol(reader)
|
|
368
363
|
r = (symbol >> 4) & 0x0F
|
|
369
364
|
s = symbol & 0x0F
|
|
370
365
|
|
|
@@ -417,7 +412,7 @@ module PureJPEG
|
|
|
417
412
|
|
|
418
413
|
def decode_block(reader, dc_tab, ac_tab, prev_dc, comp_id, out)
|
|
419
414
|
# DC coefficient
|
|
420
|
-
dc_cat = dc_tab.
|
|
415
|
+
dc_cat = dc_tab.decode_symbol(reader)
|
|
421
416
|
dc_diff = reader.receive_extend(dc_cat)
|
|
422
417
|
dc_val = prev_dc[comp_id] + dc_diff
|
|
423
418
|
prev_dc[comp_id] = dc_val
|
|
@@ -426,7 +421,7 @@ module PureJPEG
|
|
|
426
421
|
# AC coefficients
|
|
427
422
|
i = 1
|
|
428
423
|
while i < 64
|
|
429
|
-
symbol = ac_tab.
|
|
424
|
+
symbol = ac_tab.decode_symbol(reader)
|
|
430
425
|
if symbol == 0x00 # EOB
|
|
431
426
|
while i < 64
|
|
432
427
|
out[i] = 0
|
data/lib/pure_jpeg/encoder.rb
CHANGED
|
@@ -146,11 +146,9 @@ module PureJPEG
|
|
|
146
146
|
lum_dc_bits, lum_dc_values, lum_ac_bits, lum_ac_values)
|
|
147
147
|
else
|
|
148
148
|
chr_qtable = build_chr_qtable
|
|
149
|
-
y_data,
|
|
149
|
+
y_data, cb_sub, cr_sub = extract_ycbcr420(width, height)
|
|
150
150
|
sub_w = (width + 1) / 2
|
|
151
151
|
sub_h = (height + 1) / 2
|
|
152
|
-
cb_sub = downsample(cb_data, width, height, sub_w, sub_h)
|
|
153
|
-
cr_sub = downsample(cr_data, width, height, sub_w, sub_h)
|
|
154
152
|
|
|
155
153
|
lum_dc_bits, lum_dc_values, lum_ac_bits, lum_ac_values,
|
|
156
154
|
chr_dc_bits, chr_dc_values, chr_ac_bits, chr_ac_values =
|
|
@@ -215,13 +213,12 @@ module PureJPEG
|
|
|
215
213
|
padded_h = (height + 7) & ~7
|
|
216
214
|
|
|
217
215
|
block = Array.new(64, 0)
|
|
218
|
-
qbuf = Array.new(64, 0)
|
|
219
216
|
zbuf = Array.new(64, 0)
|
|
220
217
|
|
|
221
218
|
(0...padded_h).step(8) do |by|
|
|
222
219
|
(0...padded_w).step(8) do |bx|
|
|
223
220
|
extract_block_into(y_data, width, height, bx, by, block)
|
|
224
|
-
yield transform_block(block,
|
|
221
|
+
yield transform_block(block, zbuf, qtable)
|
|
225
222
|
end
|
|
226
223
|
end
|
|
227
224
|
end
|
|
@@ -285,28 +282,27 @@ module PureJPEG
|
|
|
285
282
|
mcu_h = (height + 15) & ~15
|
|
286
283
|
|
|
287
284
|
block = Array.new(64, 0)
|
|
288
|
-
qbuf = Array.new(64, 0)
|
|
289
285
|
zbuf = Array.new(64, 0)
|
|
290
286
|
|
|
291
287
|
(0...mcu_h).step(16) do |my|
|
|
292
288
|
(0...mcu_w).step(16) do |mx|
|
|
293
289
|
extract_block_into(y_data, width, height, mx, my, block)
|
|
294
|
-
yield :y, transform_block(block,
|
|
290
|
+
yield :y, transform_block(block, zbuf, lum_qt)
|
|
295
291
|
|
|
296
292
|
extract_block_into(y_data, width, height, mx + 8, my, block)
|
|
297
|
-
yield :y, transform_block(block,
|
|
293
|
+
yield :y, transform_block(block, zbuf, lum_qt)
|
|
298
294
|
|
|
299
295
|
extract_block_into(y_data, width, height, mx, my + 8, block)
|
|
300
|
-
yield :y, transform_block(block,
|
|
296
|
+
yield :y, transform_block(block, zbuf, lum_qt)
|
|
301
297
|
|
|
302
298
|
extract_block_into(y_data, width, height, mx + 8, my + 8, block)
|
|
303
|
-
yield :y, transform_block(block,
|
|
299
|
+
yield :y, transform_block(block, zbuf, lum_qt)
|
|
304
300
|
|
|
305
301
|
extract_block_into(cb_sub, sub_w, sub_h, mx >> 1, my >> 1, block)
|
|
306
|
-
yield :cb, transform_block(block,
|
|
302
|
+
yield :cb, transform_block(block, zbuf, chr_qt)
|
|
307
303
|
|
|
308
304
|
extract_block_into(cr_sub, sub_w, sub_h, mx >> 1, my >> 1, block)
|
|
309
|
-
yield :cr, transform_block(block,
|
|
305
|
+
yield :cr, transform_block(block, zbuf, chr_qt)
|
|
310
306
|
end
|
|
311
307
|
end
|
|
312
308
|
end
|
|
@@ -331,10 +327,9 @@ module PureJPEG
|
|
|
331
327
|
|
|
332
328
|
# --- Shared block pipeline (all buffers pre-allocated) ---
|
|
333
329
|
|
|
334
|
-
def transform_block(block,
|
|
330
|
+
def transform_block(block, zbuf, qtable)
|
|
335
331
|
DCT.forward!(block)
|
|
336
|
-
Quantization.
|
|
337
|
-
Zigzag.reorder!(qbuf, zbuf)
|
|
332
|
+
Quantization.quantize_zigzag!(block, qtable, zbuf)
|
|
338
333
|
zbuf
|
|
339
334
|
end
|
|
340
335
|
|
|
@@ -392,61 +387,65 @@ module PureJPEG
|
|
|
392
387
|
luminance
|
|
393
388
|
end
|
|
394
389
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
390
|
+
# Convert RGB in raster order, downsampling chroma after every two rows.
|
|
391
|
+
# Keeping only two full-resolution chroma rows avoids two image-sized arrays.
|
|
392
|
+
def extract_ycbcr420(width, height)
|
|
393
|
+
y_data = Array.new(width * height)
|
|
394
|
+
sub_w = (width + 1) / 2
|
|
395
|
+
sub_h = (height + 1) / 2
|
|
396
|
+
cb_sub = Array.new(sub_w * sub_h)
|
|
397
|
+
cr_sub = Array.new(sub_w * sub_h)
|
|
398
|
+
cb_rows = Array.new(width * 2)
|
|
399
|
+
cr_rows = Array.new(width * 2)
|
|
400
400
|
|
|
401
401
|
if source.respond_to?(:packed_pixels)
|
|
402
402
|
packed = source.packed_pixels
|
|
403
403
|
r_shift, g_shift, b_shift = packed_shifts
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
height.times do |py|
|
|
407
|
+
row = py * width
|
|
408
|
+
chroma_row = (py & 1) * width
|
|
409
|
+
if packed
|
|
410
|
+
width.times do |px|
|
|
411
|
+
color = packed[row + px]
|
|
412
|
+
r = (color >> r_shift) & 0xFF
|
|
413
|
+
g = (color >> g_shift) & 0xFF
|
|
414
|
+
b = (color >> b_shift) & 0xFF
|
|
415
|
+
y_data[row + px] = clamp255((FP_Y_R * r + FP_Y_G * g + FP_Y_B * b + FP_HALF) >> 16)
|
|
416
|
+
cb_rows[chroma_row + px] = clamp255((FP_CB_R * r + FP_CB_G * g + FP_CB_B * b + FP_128 + FP_HALF) >> 16)
|
|
417
|
+
cr_rows[chroma_row + px] = clamp255((FP_CR_R * r + FP_CR_G * g + FP_CR_B * b + FP_128 + FP_HALF) >> 16)
|
|
418
|
+
end
|
|
419
|
+
else
|
|
418
420
|
width.times do |px|
|
|
419
421
|
pixel = source[px, py]
|
|
420
422
|
r = pixel.r; g = pixel.g; b = pixel.b
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
cr_data[i] = clamp255((FP_CR_R * r + FP_CR_G * g + FP_CR_B * b + FP_128 + FP_HALF) >> 16)
|
|
423
|
+
y_data[row + px] = clamp255((FP_Y_R * r + FP_Y_G * g + FP_Y_B * b + FP_HALF) >> 16)
|
|
424
|
+
cb_rows[chroma_row + px] = clamp255((FP_CB_R * r + FP_CB_G * g + FP_CB_B * b + FP_128 + FP_HALF) >> 16)
|
|
425
|
+
cr_rows[chroma_row + px] = clamp255((FP_CR_R * r + FP_CR_G * g + FP_CR_B * b + FP_128 + FP_HALF) >> 16)
|
|
425
426
|
end
|
|
426
427
|
end
|
|
428
|
+
|
|
429
|
+
if py.odd? || py == height - 1
|
|
430
|
+
# For odd image heights, use row zero twice at the bottom edge.
|
|
431
|
+
downsample_chroma_rows(cb_rows, cr_rows, width, chroma_row,
|
|
432
|
+
cb_sub, cr_sub, sub_w, (py >> 1) * sub_w)
|
|
433
|
+
end
|
|
427
434
|
end
|
|
428
435
|
|
|
429
|
-
[y_data,
|
|
436
|
+
[y_data, cb_sub, cr_sub]
|
|
430
437
|
end
|
|
431
438
|
|
|
432
|
-
def
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
dst_row = dy * dst_w
|
|
442
|
-
dst_w.times do |dx|
|
|
443
|
-
sx = dx << 1
|
|
444
|
-
x1 = sx < max_x ? sx + 1 : max_x
|
|
445
|
-
out[dst_row + dx] = ((data[row0 + sx] + data[row0 + x1] +
|
|
446
|
-
data[row1 + sx] + data[row1 + x1]) >> 2)
|
|
447
|
-
end
|
|
439
|
+
def downsample_chroma_rows(cb_rows, cr_rows, width, row1, cb_sub, cr_sub, sub_w, dst_row)
|
|
440
|
+
max_x = width - 1
|
|
441
|
+
sub_w.times do |dx|
|
|
442
|
+
sx = dx << 1
|
|
443
|
+
x1 = sx < max_x ? sx + 1 : max_x
|
|
444
|
+
cb_sub[dst_row + dx] = (cb_rows[sx] + cb_rows[x1] +
|
|
445
|
+
cb_rows[row1 + sx] + cb_rows[row1 + x1]) >> 2
|
|
446
|
+
cr_sub[dst_row + dx] = (cr_rows[sx] + cr_rows[x1] +
|
|
447
|
+
cr_rows[row1 + sx] + cr_rows[row1 + x1]) >> 2
|
|
448
448
|
end
|
|
449
|
-
out
|
|
450
449
|
end
|
|
451
450
|
|
|
452
451
|
# Extract an 8x8 block into a pre-allocated array, level-shifted by -128.
|
|
@@ -25,13 +25,15 @@ module PureJPEG
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Decode one Huffman symbol from the bit reader.
|
|
28
|
-
def
|
|
28
|
+
def decode_symbol(reader)
|
|
29
29
|
code = 0
|
|
30
|
-
|
|
30
|
+
len = 1
|
|
31
|
+
while len <= 16
|
|
31
32
|
code = (code << 1) | reader.read_bit
|
|
32
33
|
if @max_code[len] >= 0 && code <= @max_code[len]
|
|
33
34
|
return @values[@val_ptr[len] + code - @min_code[len]]
|
|
34
35
|
end
|
|
36
|
+
len += 1
|
|
35
37
|
end
|
|
36
38
|
raise PureJPEG::DecodeError, "Invalid Huffman code"
|
|
37
39
|
end
|
|
@@ -6,14 +6,7 @@ module PureJPEG
|
|
|
6
6
|
# Return the Huffman category (bit length) for a value.
|
|
7
7
|
# Avoids Array allocation compared to the combined category_and_bits.
|
|
8
8
|
def self.category(value)
|
|
9
|
-
|
|
10
|
-
v = value.abs
|
|
11
|
-
cat = 0
|
|
12
|
-
while v > 0
|
|
13
|
-
cat += 1
|
|
14
|
-
v >>= 1
|
|
15
|
-
end
|
|
16
|
-
cat
|
|
9
|
+
value.abs.bit_length
|
|
17
10
|
end
|
|
18
11
|
|
|
19
12
|
# Return the extra bits to encode for a value with the given category.
|
|
@@ -50,6 +43,7 @@ module PureJPEG
|
|
|
50
43
|
end
|
|
51
44
|
|
|
52
45
|
yield 0x00, 0 if last_nonzero < 63
|
|
46
|
+
nil
|
|
53
47
|
end
|
|
54
48
|
|
|
55
49
|
def self.each_ac_symbol(zigzag)
|
|
@@ -85,12 +79,12 @@ module PureJPEG
|
|
|
85
79
|
end
|
|
86
80
|
|
|
87
81
|
def encode_ac(zigzag, writer)
|
|
88
|
-
|
|
82
|
+
PureJPEG::Huffman::Encoder.each_ac_item(zigzag) do |symbol, value|
|
|
89
83
|
code, length = @ac_table[symbol]
|
|
90
84
|
writer.write_bits(code, length)
|
|
91
85
|
next if symbol == 0x00 || symbol == 0xF0
|
|
92
86
|
|
|
93
|
-
cat =
|
|
87
|
+
cat = symbol & 0x0F
|
|
94
88
|
writer.write_bits(self.class.value_bits(value, cat), cat)
|
|
95
89
|
end
|
|
96
90
|
end
|
|
@@ -58,5 +58,33 @@ module PureJPEG
|
|
|
58
58
|
64.times { |i| out[i] = block[i] * table[i] }
|
|
59
59
|
out
|
|
60
60
|
end
|
|
61
|
+
|
|
62
|
+
# Quantize raster-order coefficients directly into zigzag order.
|
|
63
|
+
def self.quantize_zigzag!(block, table, out)
|
|
64
|
+
order = Zigzag::ORDER
|
|
65
|
+
i = 0
|
|
66
|
+
while i < 64
|
|
67
|
+
j = order[i]
|
|
68
|
+
v = block[j]; t = table[j]
|
|
69
|
+
out[i] = if v >= 0
|
|
70
|
+
(v + (t >> 1)) / t
|
|
71
|
+
else
|
|
72
|
+
-((-v + (t >> 1)) / t)
|
|
73
|
+
end
|
|
74
|
+
i += 1
|
|
75
|
+
end
|
|
76
|
+
out
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Dequantize zigzag coefficients directly into raster order. The offset
|
|
80
|
+
# allows progressive decoding to read from its full coefficient buffer.
|
|
81
|
+
def self.dequantize_zigzag!(block, table, out, offset = 0)
|
|
82
|
+
order = Zigzag::ORDER
|
|
83
|
+
64.times do |i|
|
|
84
|
+
j = order[i]
|
|
85
|
+
out[j] = block[offset + i] * table[j]
|
|
86
|
+
end
|
|
87
|
+
out
|
|
88
|
+
end
|
|
61
89
|
end
|
|
62
90
|
end
|
|
@@ -27,15 +27,16 @@ module PureJPEG
|
|
|
27
27
|
def initialize(width, height, &block)
|
|
28
28
|
@width = width
|
|
29
29
|
@height = height
|
|
30
|
-
@pixels = Array.new(width * height) { Pixel.new(0, 0, 0) }
|
|
31
|
-
|
|
32
30
|
if block
|
|
31
|
+
@pixels = Array.new(width * height)
|
|
33
32
|
height.times do |y|
|
|
34
33
|
width.times do |x|
|
|
35
34
|
r, g, b = block.call(x, y)
|
|
36
35
|
@pixels[y * width + x] = Pixel.new(r, g, b)
|
|
37
36
|
end
|
|
38
37
|
end
|
|
38
|
+
else
|
|
39
|
+
@pixels = Array.new(width * height) { Pixel.new(0, 0, 0) }
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
data/lib/pure_jpeg/version.rb
CHANGED