1 // Baseline JPEG decoder
2 // adapted from https://github.com/lgvz/imageformats
3 // Boost License, i suppose
11 // ////////////////////////////////////////////////////////////////////////// //
12 public class ImageIOException
: Exception
{
13 this (string msg
, string file
=__FILE__
, size_t line
=__LINE__
, Throwable next
=null) const pure nothrow @safe @nogc {
14 super(msg
, file
, line
, next
);
19 // ////////////////////////////////////////////////////////////////////////// //
20 // public declarations
21 public bool detectJpeg (VFile stream
) {
24 readJpegInfo(stream
, w
, h
, c
);
29 stream
.seek(0, Seek
.Set
);
34 public void readJpegInfo (VFile stream
, out int w
, out int h
, out int chans
) {
35 import std
.bitmanip
: bigEndianToNative
;
37 ubyte[2] marker
= void;
38 stream
.rawReadExact(marker
[]);
41 if (marker
[0..2] != [0xff, 0xd8]) throw new ImageIOException("not JPEG");
44 stream
.rawReadExact(marker
[]);
45 if (marker
[0] != 0xff) throw new ImageIOException("no frame header");
46 while (marker
[1] == 0xff) stream
.rawReadExact(marker
[1..$]);
48 switch (marker
[1]) with (Marker
) {
49 case SOF0
: .. case SOF3
: goto case;
50 case SOF9
: .. case SOF11
:
52 stream
.rawReadExact(tmp
[0..8]);
53 //int len = bigEndianToNative!ushort(tmp[0..2]);
54 w
= bigEndianToNative
!ushort(tmp
[5..7]);
55 h
= bigEndianToNative
!ushort(tmp
[3..5]);
58 case SOS
, EOI
: throw new ImageIOException("no frame header");
59 case DRI
, DHT
, DQT
, COM
: goto case SKIP
;
60 case APP0
: .. case APPf
: goto case SKIP
;
62 ubyte[2] lenbuf
= void;
63 stream
.rawReadExact(lenbuf
[]);
64 int skiplen
= bigEndianToNative
!ushort(lenbuf
)-2;
65 stream
.seek(skiplen
, Seek
.Cur
);
67 default: throw new ImageIOException("unsupported marker");
74 public TrueColorImage
readJpeg (VFile stream
) {
79 stream
.rawReadExact(tmp
[]);
80 if (tmp
[0..2] != [0xff, 0xd8]) throw new ImageIOException("not JPEG");
82 JPEG_Decoder dc
;// = { stream: stream }; // DMD bug: no postblit is called
85 read_markers(dc
); // reads until first scan header or eoi
86 if (dc
.eoi_reached
) throw new ImageIOException("no image data");
88 dc
.tgt_chans
= (req_chans
== 0 ? dc
.num_comps
: cast(int)req_chans
);
90 auto pixels
= decode_jpeg(dc
);
91 //assert(pixels.length == dc.width*dc.height*4);
92 return new TrueColorImage(dc
.width
, dc
.height
, pixels
);
96 public TrueColorImage
readJpeg (const(char)[] fname
) { return readJpeg(VFile(fname
)); }
99 // ////////////////////////////////////////////////////////////////////////// //
101 struct JPEG_Decoder
{
102 @disable this (this); // just in case
106 bool has_frame_header
= false;
107 bool eoi_reached
= false;
109 ubyte[64][4] qtables
;
110 HuffTab
[2] ac_tables
;
111 HuffTab
[2] dc_tables
;
113 ubyte cb
; // current byte (next bit always at MSB)
114 int bits_left
; // num of unused bits in cb
116 bool correct_comp_ids
;
125 ushort restart_interval
; // number of MCUs in restart interval
128 static struct Component
{
129 ubyte sfx
, sfy
; // sampling factors, aka. h and v
130 size_t x
, y
; // total num of samples, without fill samples
134 int pred
; // dc prediction
135 ubyte[] data
; // reconstructed samples
146 short[16] mincode
, maxcode
;
151 enum Marker
: ubyte {
152 SOI
= 0xd8, // start of image
153 SOF0
= 0xc0, // start of frame / baseline DCT
154 //SOF1 = 0xc1, // start of frame / extended seq.
155 //SOF2 = 0xc2, // start of frame / progressive DCT
156 SOF3
= 0xc3, // start of frame / lossless
157 SOF9
= 0xc9, // start of frame / extended seq., arithmetic
158 SOF11
= 0xcb, // start of frame / lossless, arithmetic
159 DHT
= 0xc4, // define huffman tables
160 DQT
= 0xdb, // define quantization tables
161 DRI
= 0xdd, // define restart interval
162 SOS
= 0xda, // start of scan
163 DNL
= 0xdc, // define number of lines
164 RST0
= 0xd0, // restart entropy coded data
166 RST7
= 0xd7, // restart entropy coded data
167 APP0
= 0xe0, // application 0 segment
169 APPf
= 0xef, // application f segment
170 //DAC = 0xcc, // define arithmetic conditioning table
171 COM
= 0xfe, // comment
172 EOI
= 0xd9, // end of image
176 void read_markers (ref JPEG_Decoder dc
) {
177 import std
.bitmanip
: bigEndianToNative
;
178 bool has_next_scan_header
= false;
179 while (!has_next_scan_header
&& !dc
.eoi_reached
) {
180 ubyte[2] marker
= void;
181 dc
.stream
.rawReadExact(marker
[]);
182 if (marker
[0] != 0xff) throw new ImageIOException("no marker");
183 while (marker
[1] == 0xff) dc
.stream
.rawReadExact(marker
[1..$]);
184 debug(DebugJPEG
) writefln("marker: %s (%1$x)\t", cast(Marker
)marker
[1]);
185 switch (marker
[1]) with (Marker
) {
186 case DHT
: dc
.read_huffman_tables(); break;
187 case DQT
: dc
.read_quantization_tables(); break;
189 if (dc
.has_frame_header
) throw new ImageIOException("extra frame header");
190 debug(DebugJPEG
) writeln();
191 dc
.read_frame_header();
192 dc
.has_frame_header
= true;
195 if (!dc
.has_frame_header
) throw new ImageIOException("no frame header");
196 dc
.read_scan_header();
197 has_next_scan_header
= true;
199 case DRI
: dc
.read_restart_interval(); break;
200 case EOI
: dc
.eoi_reached
= true; break;
201 case APP0
: .. case APPf
: goto case;
203 debug(DebugJPEG
) writefln("-> skipping segment");
204 ubyte[2] lenbuf
= void;
205 dc
.stream
.rawReadExact(lenbuf
[]);
206 int len
= bigEndianToNative
!ushort(lenbuf
)-2;
207 dc
.stream
.seek(len
, Seek
.Cur
);
209 default: throw new ImageIOException("invalid / unsupported marker");
215 // DHT -- define huffman tables
216 void read_huffman_tables (ref JPEG_Decoder dc
) {
217 import std
.bitmanip
: bigEndianToNative
;
218 ubyte[19] tmp
= void;
219 dc
.stream
.rawReadExact(tmp
[0..2]);
220 int len
= bigEndianToNative
!ushort(tmp
[0..2]);
223 dc
.stream
.rawReadExact(tmp
[0..17]); // info byte & the BITS
224 ubyte table_slot
= tmp
[0]&0xf; // must be 0 or 1 for baseline
225 ubyte table_class
= tmp
[0]>>4; // 0 = dc table, 1 = ac table
226 if (1 < table_slot ||
1 < table_class
) throw new ImageIOException("invalid / not supported");
227 // compute total number of huffman codes
229 foreach (immutable i
; 1..17) mt
+= tmp
[i
];
230 if (256 < mt
) throw new ImageIOException("invalid / not supported"); // TODO where in the spec?
231 if (table_class
== 0) {
232 dc
.stream
.rawReadExact(dc
.dc_tables
[table_slot
].values
[0..mt
]);
233 derive_table(dc
.dc_tables
[table_slot
], tmp
[1..17]);
235 dc
.stream
.rawReadExact(dc
.ac_tables
[table_slot
].values
[0..mt
]);
236 derive_table(dc
.ac_tables
[table_slot
], tmp
[1..17]);
243 // num_values is the BITS
244 void derive_table (ref HuffTab table
, in ref ubyte[16] num_values
) {
247 foreach (immutable i
; 0..16) {
248 foreach (immutable j
; 0..num_values
[i
]) {
249 table
.sizes
[k
] = cast(ubyte)(i
+1);
256 ubyte si
= table
.sizes
[k
];
262 } while (si
== table
.sizes
[k
]);
263 if (table
.sizes
[k
] == 0) break;
264 debug(DebugJPEG
) assert(si
< table
.sizes
[k
]);
268 } while (si
!= table
.sizes
[k
]);
270 derive_mincode_maxcode_valptr(table
.mincode
, table
.maxcode
, table
.valptr
, codes
, num_values
);
275 void derive_mincode_maxcode_valptr (ref short[16] mincode
, ref short[16] maxcode
, ref short[16] valptr
, in ref short[256] codes
, in ref ubyte[16] num_values
) pure {
280 foreach (immutable i
; 0..16) {
281 if (num_values
[i
] != 0) {
282 valptr
[i
] = cast(short)j
;
283 mincode
[i
] = codes
[j
];
284 j
+= num_values
[i
]-1;
285 maxcode
[i
] = codes
[j
];
292 // DQT -- define quantization tables
293 void read_quantization_tables (ref JPEG_Decoder dc
) {
294 import std
.bitmanip
: bigEndianToNative
;
296 dc
.stream
.rawReadExact(tmp
[0..2]);
297 int len
= bigEndianToNative
!ushort(tmp
[0..2]);
298 if (len
%65 != 2) throw new ImageIOException("invalid / not supported");
301 dc
.stream
.rawReadExact(tmp
[0..1]);
302 ubyte table_info
= tmp
[0];
303 ubyte table_slot
= table_info
&0xf;
304 ubyte precision
= table_info
>>4; // 0 = 8 bit, 1 = 16 bit
305 if (3 < table_slot || precision
!= 0) throw new ImageIOException("invalid / not supported"); // only 8 bit for baseline
306 dc
.stream
.rawReadExact(dc
.qtables
[table_slot
][0..64]);
312 // SOF0 -- start of frame
313 void read_frame_header (ref JPEG_Decoder dc
) {
314 import std
.bitmanip
: bigEndianToNative
;
316 dc
.stream
.rawReadExact(tmp
[0..8]);
317 int len
= bigEndianToNative
!ushort(tmp
[0..2]); // 8+num_comps*3
318 ubyte precision
= tmp
[2];
319 dc
.height
= bigEndianToNative
!ushort(tmp
[3..5]);
320 dc
.width
= bigEndianToNative
!ushort(tmp
[5..7]);
321 dc
.num_comps
= tmp
[7];
322 if (precision
!= 8 ||
(dc
.num_comps
!= 1 && dc
.num_comps
!= 3) || len
!= 8+dc
.num_comps
*3) throw new ImageIOException("invalid / not supported");
325 int mcu_du
= 0; // data units in one mcu
326 dc
.stream
.rawReadExact(tmp
[0..dc
.num_comps
*3]);
327 foreach (immutable i
; 0..dc
.num_comps
) {
329 // JFIF says ci should be i+1, but there are images where ci is i. Normalize ids
330 // so that ci == i, always. So much for standards...
331 if (i
== 0) { dc
.correct_comp_ids
= ci
== i
+1; }
332 if ((dc
.correct_comp_ids
&& ci
!= i
+1) ||
(!dc
.correct_comp_ids
&& ci
!= i
)) throw new ImageIOException("invalid component id");
333 auto comp
= &dc
.comps
[i
];
334 ubyte sampling_factors
= tmp
[i
*3+1];
335 comp
.sfx
= sampling_factors
>>4;
336 comp
.sfy
= sampling_factors
&0xf;
337 comp
.qtable
= tmp
[i
*3+2];
338 if (comp
.sfy
< 1 ||
4 < comp
.sfy || comp
.sfx
< 1 ||
4 < comp
.sfx ||
3 < comp
.qtable
) throw new ImageIOException("invalid / not supported");
339 if (dc
.hmax
< comp
.sfx
) dc
.hmax
= comp
.sfx
;
340 if (dc
.vmax
< comp
.sfy
) dc
.vmax
= comp
.sfy
;
341 mcu_du
+= comp
.sfx
*comp
.sfy
;
343 if (10 < mcu_du
) throw new ImageIOException("invalid / not supported");
344 foreach (immutable i
; 0..dc
.num_comps
) {
345 import std
.math
: ceil
;
346 dc
.comps
[i
].x
= cast(size_t
)ceil(dc
.width
*(cast(double)dc
.comps
[i
].sfx
/dc
.hmax
));
347 dc
.comps
[i
].y
= cast(size_t
)ceil(dc
.height
*(cast(double)dc
.comps
[i
].sfy
/dc
.vmax
));
348 debug(DebugJPEG
) writefln("%d comp %d sfx/sfy: %d/%d", i
, dc
.comps
[i
].id
, dc
.comps
[i
].sfx
, dc
.comps
[i
].sfy
);
350 size_t mcu_w
= dc
.hmax
*8;
351 size_t mcu_h
= dc
.vmax
*8;
352 dc
.num_mcu_x
= cast(int)((dc
.width
+mcu_w
-1)/mcu_w
);
353 dc
.num_mcu_y
= cast(int)((dc
.height
+mcu_h
-1)/mcu_h
);
355 writefln("\tlen: %s", len
);
356 writefln("\tprecision: %s", precision
);
357 writefln("\tdimensions: %s x %s", dc
.width
, dc
.height
);
358 writefln("\tnum_comps: %s", dc
.num_comps
);
359 writefln("\tnum_mcu_x: %s", dc
.num_mcu_x
);
360 writefln("\tnum_mcu_y: %s", dc
.num_mcu_y
);
365 // SOS -- start of scan
366 void read_scan_header (ref JPEG_Decoder dc
) {
367 import std
.bitmanip
: bigEndianToNative
;
368 import core
.stdc
.stdlib
: alloca
;
370 dc
.stream
.rawReadExact(tmp
[]);
371 ushort len
= bigEndianToNative
!ushort(tmp
[0..2]);
372 ubyte num_scan_comps
= tmp
[2];
373 if (num_scan_comps
!= dc
.num_comps || len
!= 6+num_scan_comps
*2) throw new ImageIOException("invalid / not supported");
374 auto buf
= (cast(ubyte*)alloca((len
-3)*ubyte.sizeof
))[0..len
-3];
375 dc
.stream
.rawReadExact(buf
[]);
376 foreach (immutable i
; 0..num_scan_comps
) {
377 uint ci
= buf
[i
*2]-((dc
.correct_comp_ids
) ?
1 : 0);
378 if (ci
>= dc
.num_comps
) throw new ImageIOException("invalid component id");
379 ubyte tables
= buf
[i
*2+1];
380 dc
.comps
[ci
].dc_table
= tables
>>4;
381 dc
.comps
[ci
].ac_table
= tables
&0xf;
382 if (1 < dc
.comps
[ci
].dc_table ||
1 < dc
.comps
[ci
].ac_table
) throw new ImageIOException("invalid / not supported");
385 //ubyte spectral_start = buf[$-3];
386 //ubyte spectral_end = buf[$-2];
387 //ubyte approx = buf[$-1];
391 void read_restart_interval (ref JPEG_Decoder dc
) {
392 import std
.bitmanip
: bigEndianToNative
;
394 dc
.stream
.rawReadExact(tmp
[]);
395 ushort len
= bigEndianToNative
!ushort(tmp
[0..2]);
396 if (len
!= 4) throw new ImageIOException("invalid / not supported");
397 dc
.restart_interval
= bigEndianToNative
!ushort(tmp
[2..4]);
398 debug(DebugJPEG
) writeln("restart interval set to: ", dc
.restart_interval
);
402 // reads data after the SOS segment
403 ubyte[] decode_jpeg (ref JPEG_Decoder dc
) {
404 foreach (ref comp
; dc
.comps
[0..dc
.num_comps
]) comp
.data
= new ubyte[dc
.num_mcu_x
*comp
.sfx
*8*dc
.num_mcu_y
*comp
.sfy
*8];
405 // E.7 -- Multiple scans are for progressive images which are not supported
406 //while (!dc.eoi_reached) {
407 decode_scan(dc
); // E.2.3
408 //read_markers(dc); // reads until next scan header or eoi
410 // throw away fill samples and convert to target format
411 return dc
.reconstruct();
415 // E.2.3 and E.8 and E.9
416 void decode_scan (ref JPEG_Decoder dc
) {
417 debug(DebugJPEG
) writeln("decode scan...");
419 if (0 < dc
.restart_interval
) {
420 int total_mcus
= dc
.num_mcu_x
*dc
.num_mcu_y
;
421 intervals
= (total_mcus
+dc
.restart_interval
-1)/dc
.restart_interval
;
422 mcus
= dc
.restart_interval
;
425 mcus
= dc
.num_mcu_x
*dc
.num_mcu_y
;
427 debug(DebugJPEG
) writeln("intervals: ", intervals
);
428 foreach (immutable mcu_j
; 0..dc
.num_mcu_y
) {
429 foreach (immutable mcu_i
; 0..dc
.num_mcu_x
) {
431 foreach (immutable c
; 0..dc
.num_comps
) {
432 auto comp
= &dc
.comps
[c
];
433 foreach (immutable du_j
; 0..comp
.sfy
) {
434 foreach (immutable du_i
; 0..comp
.sfx
) {
435 // decode entropy, dequantize & dezigzag
436 short[64] data
= decode_block(dc
, *comp
, dc
.qtables
[comp
.qtable
]);
437 // idct & level-shift
438 int outx
= (mcu_i
*comp
.sfx
+du_i
)*8;
439 int outy
= (mcu_j
*comp
.sfy
+du_j
)*8;
440 int dst_stride
= dc
.num_mcu_x
*comp
.sfx
*8;
441 ubyte* dst
= comp
.data
.ptr
+outy
*dst_stride
+outx
;
442 stbi__idct_block(dst
, dst_stride
, data
);
449 if (!intervals
) return;
450 read_restart(dc
.stream
); // RSTx marker
451 if (intervals
== 1) {
452 // last interval, may have fewer MCUs than defined by DRI
453 mcus
= (dc
.num_mcu_y
-mcu_j
-1)*dc
.num_mcu_x
+dc
.num_mcu_x
-mcu_i
-1;
455 mcus
= dc
.restart_interval
;
460 foreach (immutable k
; 0..dc
.num_comps
) dc
.comps
[k
].pred
= 0;
468 void read_restart (VFile stream
) {
470 stream
.rawReadExact(tmp
[]);
471 if (tmp
[0] != 0xff || tmp
[1] < Marker
.RST0 || Marker
.RST7
< tmp
[1]) throw new ImageIOException("reset marker missing");
472 // the markers should cycle 0 through 7, could check that here...
476 immutable ubyte[64] dezigzag
= [
477 0, 1, 8, 16, 9, 2, 3, 10,
478 17, 24, 32, 25, 18, 11, 4, 5,
479 12, 19, 26, 33, 40, 48, 41, 34,
480 27, 20, 13, 6, 7, 14, 21, 28,
481 35, 42, 49, 56, 57, 50, 43, 36,
482 29, 22, 15, 23, 30, 37, 44, 51,
483 58, 59, 52, 45, 38, 31, 39, 46,
484 53, 60, 61, 54, 47, 55, 62, 63,
488 // decode entropy, dequantize & dezigzag (see section F.2)
489 short[64] decode_block (ref JPEG_Decoder dc
, ref JPEG_Decoder
.Component comp
, in ref ubyte[64] qtable
) {
491 ubyte t
= decode_huff(dc
, dc
.dc_tables
[comp
.dc_table
]);
492 int diff
= t ? dc
.receive_and_extend(t
) : 0;
493 comp
.pred
= comp
.pred
+diff
;
494 res
[0] = cast(short)(comp
.pred
*qtable
[0]);
497 ubyte rs
= decode_huff(dc
, dc
.ac_tables
[comp
.ac_table
]);
501 if (rrrr
!= 0xf) break; // end of block
502 k
+= 16; // run length is 16
506 if (63 < k
) throw new ImageIOException("corrupt block");
507 res
[dezigzag
[k
]] = cast(short)(dc
.receive_and_extend(ssss
)*qtable
[k
]);
514 int receive_and_extend (ref JPEG_Decoder dc
, ubyte s
) {
517 foreach (immutable _
; 0..s
) symbol
= (symbol
<<1)+nextbit(dc
);
520 if (symbol
< vt
) return symbol
+(-1<<s
)+1;
525 // F.16 -- the DECODE
526 ubyte decode_huff (ref JPEG_Decoder dc
, in ref HuffTab tab
) {
527 short code
= nextbit(dc
);
529 while (tab
.maxcode
[i
] < code
) {
530 code
= cast(short)((code
<<1)+nextbit(dc
));
532 if (tab
.maxcode
.length
<= i
) throw new ImageIOException("corrupt huffman coding");
534 int j
= tab
.valptr
[i
]+code
-tab
.mincode
[i
];
535 if (tab
.values
.length
<= cast(uint)j
) throw new ImageIOException("corrupt huffman coding");
536 return tab
.values
[j
];
541 ubyte nextbit (ref JPEG_Decoder dc
) {
543 ubyte[1] bytebuf
= void;
544 dc
.stream
.rawReadExact(bytebuf
[]);
548 dc
.stream
.rawReadExact(bytebuf
[]);
549 if (bytebuf
[0] != 0x0) throw new ImageIOException("unexpected marker");
559 ubyte[] reconstruct (in ref JPEG_Decoder dc
) {
560 auto result
= new ubyte[dc
.width
*dc
.height
*dc
.tgt_chans
];
561 switch (dc
.num_comps
*10+dc
.tgt_chans
) {
563 // Use specialized bilinear filtering functions for the frequent cases where
564 // Cb & Cr channels have half resolution.
565 if ((dc
.comps
[0].sfx
<= 2 && dc
.comps
[0].sfy
<= 2) &&
566 (dc
.comps
[0].sfx
+dc
.comps
[0].sfy
>= 3) &&
567 dc
.comps
[1].sfx
== 1 && dc
.comps
[1].sfy
== 1 &&
568 dc
.comps
[2].sfx
== 1 && dc
.comps
[2].sfy
== 1) {
569 void function (in ubyte[], in ubyte[], ubyte[]) resample
;
570 switch (dc
.comps
[0].sfx
*10+dc
.comps
[0].sfy
) {
571 case 22: resample
= &upsample_h2_v2
; break;
572 case 21: resample
= &upsample_h2_v1
; break;
573 case 12: resample
= &upsample_h1_v2
; break;
574 default: throw new ImageIOException("bug");
576 auto comp1
= new ubyte[](dc
.width
);
577 auto comp2
= new ubyte[](dc
.width
);
580 foreach (immutable j
; 0..dc
.height
) {
581 size_t mi
= j
/dc
.comps
[0].sfy
;
582 size_t si
= (mi
== 0 || mi
>= (dc
.height
-1)/dc
.comps
[0].sfy ? mi
: mi
-1+s
*2);
584 size_t cs
= dc
.num_mcu_x
*dc
.comps
[1].sfx
*8;
587 resample(dc
.comps
[1].data
[cl0
..cl0
+dc
.comps
[1].x
], dc
.comps
[1].data
[cl1
..cl1
+dc
.comps
[1].x
], comp1
[]);
588 resample(dc
.comps
[2].data
[cl0
..cl0
+dc
.comps
[2].x
], dc
.comps
[2].data
[cl1
..cl1
+dc
.comps
[2].x
], comp2
[]);
589 foreach (immutable i
; 0..dc
.width
) {
590 result
[di..di+3] = ycbcr_to_rgb(dc
.comps
[0].data
[j
*dc
.num_mcu_x
*dc
.comps
[0].sfx
*8+i
], comp1
[i
], comp2
[i
]);
591 if (dc
.tgt_chans
== 4) result
[di+3] = 255;
597 foreach (const ref comp
; dc
.comps
[0..dc
.num_comps
]) {
598 if (comp
.sfx
!= dc
.hmax || comp
.sfy
!= dc
.vmax
) return dc
.upsample(result
);
601 foreach (immutable j
; 0..dc
.height
) {
602 foreach (immutable i
; 0..dc
.width
) {
603 result
[di..di+3] = ycbcr_to_rgb(dc
.comps
[0].data
[si
+i
], dc
.comps
[1].data
[si
+i
], dc
.comps
[2].data
[si
+i
]);
604 if (dc
.tgt_chans
== 4) result
[di+3] = 255;
607 si
+= dc
.num_mcu_x
*dc
.comps
[0].sfx
*8;
611 const comp
= &dc
.comps
[0];
612 if (comp
.sfx
== dc
.hmax
&& comp
.sfy
== dc
.vmax
) {
614 if (dc
.tgt_chans
== 2) {
615 foreach (immutable j
; 0..dc
.height
) {
616 foreach (immutable i
; 0..dc
.width
) {
617 result
[di++] = comp
.data
[si
+i
];
620 si
+= dc
.num_mcu_x
*comp
.sfx
*8;
623 foreach (immutable j
; 0..dc
.height
) {
624 result
[di..di+dc
.width
] = comp
.data
[si
..si
+dc
.width
];
625 si
+= dc
.num_mcu_x
*comp
.sfx
*8;
631 // need to resample (haven't tested this...)
632 return dc
.upsample_luma(result
);
634 const comp
= &dc
.comps
[0];
636 foreach (immutable j
; 0..dc
.height
) {
637 foreach (immutable i
; 0..dc
.width
) {
638 result
[di..di+3] = comp
.data
[si
+i
];
639 if (dc
.tgt_chans
== 4) result
[di+3] = 255;
642 si
+= dc
.num_mcu_x
*comp
.sfx
*8;
650 void upsample_h2_v2(in ubyte[] line0
, in ubyte[] line1
, ubyte[] result
) {
651 ubyte mix() (ubyte mm
, ubyte ms
, ubyte sm
, ubyte ss
) {
652 pragma(inline
, true);
653 return cast(ubyte)((cast(uint)mm
*3*3+cast(uint)ms
*3*1+cast(uint)sm
*1*3+cast(uint)ss
*1*1+8)/16);
656 result
[0] = cast(ubyte)((cast(uint)line0
[0]*3+cast(uint)line1
[0]*1+2)/4);
657 if (line0
.length
== 1) return;
658 result
[1] = mix(line0
[0], line0
[1], line1
[0], line1
[1]);
661 foreach (immutable i
; 1..line0
.length
) {
662 result
[di] = mix(line0
[i
], line0
[i
-1], line1
[i
], line1
[i
-1]);
664 if (i
== line0
.length
-1) {
665 if (di < result
.length
) result
[di] = cast(ubyte)((cast(uint)line0
[i
]*3+cast(uint)line1
[i
]*1+2)/4);
668 result
[di] = mix(line0
[i
], line0
[i
+1], line1
[i
], line1
[i
+1]);
674 void upsample_h2_v1 (in ubyte[] line0
, in ubyte[] _line1
, ubyte[] result
) {
675 result
[0] = line0
[0];
676 if (line0
.length
== 1) return;
677 result
[1] = cast(ubyte)((cast(uint)line0
[0]*3+cast(uint)line0
[1]*1+2)/4);
679 foreach (immutable i
; 1..line0
.length
) {
680 result
[di] = cast(ubyte)((cast(uint)line0
[i
-1]*1+cast(uint)line0
[i
+0]*3+2)/4);
682 if (i
== line0
.length
-1) {
683 if (di < result
.length
) result
[di] = line0
[i
];
686 result
[di] = cast(ubyte)((cast(uint)line0
[i
+0]*3+cast(uint)line0
[i
+1]*1+2)/4);
692 void upsample_h1_v2 (in ubyte[] line0
, in ubyte[] line1
, ubyte[] result
) {
693 foreach (immutable i
; 0..result
.length
) result
[i
] = cast(ubyte)((cast(uint)line0
[i
]*3+cast(uint)line1
[i
]*1+2)/4);
698 ubyte[] upsample_luma (in ref JPEG_Decoder dc
, ubyte[] result
) {
699 const size_t stride0
= dc
.num_mcu_x
*dc
.comps
[0].sfx
*8;
700 const y_step0
= cast(float)dc
.comps
[0].sfy
/cast(float)dc
.vmax
;
701 const x_step0
= cast(float)dc
.comps
[0].sfx
/cast(float)dc
.hmax
;
702 float y0
= y_step0
*0.5;
705 foreach (immutable j
; 0..dc
.height
) {
706 float x0
= x_step0
*0.5;
708 foreach (immutable i
; 0..dc
.width
) {
709 result
[di] = dc
.comps
[0].data
[y0i
+x0i
];
710 if (dc
.tgt_chans
== 2) result
[di+1] = 255;
713 if (x0
>= 1.0) { x0
-= 1.0; x0i
+= 1; }
716 if (y0
>= 1.0) { y0
-= 1.0; y0i
+= stride0
; }
723 ubyte[] upsample (in ref JPEG_Decoder dc
, ubyte[] result
) {
724 const size_t stride0
= dc
.num_mcu_x
*dc
.comps
[0].sfx
*8;
725 const size_t stride1
= dc
.num_mcu_x
*dc
.comps
[1].sfx
*8;
726 const size_t stride2
= dc
.num_mcu_x
*dc
.comps
[2].sfx
*8;
727 const y_step0
= cast(float)dc
.comps
[0].sfy
/cast(float)dc
.vmax
;
728 const y_step1
= cast(float)dc
.comps
[1].sfy
/cast(float)dc
.vmax
;
729 const y_step2
= cast(float)dc
.comps
[2].sfy
/cast(float)dc
.vmax
;
730 const x_step0
= cast(float)dc
.comps
[0].sfx
/cast(float)dc
.hmax
;
731 const x_step1
= cast(float)dc
.comps
[1].sfx
/cast(float)dc
.hmax
;
732 const x_step2
= cast(float)dc
.comps
[2].sfx
/cast(float)dc
.hmax
;
733 float y0
= y_step0
*0.5;
734 float y1
= y_step1
*0.5;
735 float y2
= y_step2
*0.5;
740 foreach (immutable _j
; 0..dc
.height
) {
741 float x0
= x_step0
*0.5;
742 float x1
= x_step1
*0.5;
743 float x2
= x_step2
*0.5;
747 foreach (immutable i
; 0..dc
.width
) {
748 result
[di..di+3] = ycbcr_to_rgb(dc
.comps
[0].data
[y0i
+x0i
], dc
.comps
[1].data
[y1i
+x1i
], dc
.comps
[2].data
[y2i
+x2i
]);
749 if (dc
.tgt_chans
== 4) result
[di+3] = 255;
754 if (x0
>= 1.0) { x0
-= 1.0; x0i
+= 1; }
755 if (x1
>= 1.0) { x1
-= 1.0; x1i
+= 1; }
756 if (x2
>= 1.0) { x2
-= 1.0; x2i
+= 1; }
761 if (y0
>= 1.0) { y0
-= 1.0; y0i
+= stride0
; }
762 if (y1
>= 1.0) { y1
-= 1.0; y1i
+= stride1
; }
763 if (y2
>= 1.0) { y2
-= 1.0; y2i
+= stride2
; }
769 ubyte[3] ycbcr_to_rgb (ubyte y
, ubyte cb
, ubyte cr
) pure {
771 rgb
[0] = clamp(y
+1.402*(cr
-128));
772 rgb
[1] = clamp(y
-0.34414*(cb
-128)-0.71414*(cr
-128));
773 rgb
[2] = clamp(y
+1.772*(cb
-128));
778 ubyte clamp() (float x
) pure {
780 if (255 < x
) return 255;
785 // ------------------------------------------------------------
786 // The IDCT stuff here (to the next dashed line) is copied and adapted from
787 // stb_image which is released under public domain. Many thanks to stb_image
788 // author, Sean Barrett.
789 // Link: https://github.com/nothings/stb/blob/master/stb_image.h
790 int f2f() (float x
) pure { pragma(inline
, true); return cast(int)(x
*4096+0.5); }
791 int fsh() (int x
) pure { pragma(inline
, true); return x
<<12; }
793 // from stb_image, derived from jidctint -- DCT_ISLOW
794 void STBI__IDCT_1D() (ref int t0
, ref int t1
, ref int t2
, ref int t3
,
795 ref int x0
, ref int x1
, ref int x2
, ref int x3
,
796 int s0
, int s1
, int s2
, int s3
, int s4
, int s5
, int s6
, int s7
) pure
798 int p1
, p2
, p3
, p4
, p5
;
799 //int t0, t1, t2, t3, p1, p2, p3, p4, p5, x0, x1, x2, x3;
802 p1
= (p2
+p3
)*f2f(0.5411961f);
803 t2
= p1
+p3
*f2f(-1.847759065f);
804 t3
= p1
+p2
*f2f(0.765366865f);
821 p5
= (p3
+p4
)*f2f(1.175875602f);
822 t0
= t0
*f2f(0.298631336f);
823 t1
= t1
*f2f(2.053119869f);
824 t2
= t2
*f2f(3.072711026f);
825 t3
= t3
*f2f(1.501321110f);
826 p1
= p5
+p1
*f2f(-0.899976223f);
827 p2
= p5
+p2
*f2f(-2.562915447f);
828 p3
= p3
*f2f(-1.961570560f);
829 p4
= p4
*f2f(-0.390180644f);
836 // idct and level-shift
837 void stbi__idct_block (ubyte* dst
, int dst_stride
, in ref short[64] data
) pure {
841 const(short)* d
= data
.ptr
;
843 for (i
= 0; i
< 8; ++i
, ++d
, ++v
) {
844 // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
845 if (d
[8] == 0 && d
[16] == 0 && d
[24] == 0 && d
[32] == 0 && d
[40] == 0 && d
[48] == 0 && d
[56] == 0) {
846 // no shortcut 0 seconds
847 // (1|2|3|4|5|6|7)==0 0 seconds
848 // all separate -0.047 seconds
849 // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
850 int dcterm
= d
[0]<<2;
851 v
[0] = v
[8] = v
[16] = v
[24] = v
[32] = v
[40] = v
[48] = v
[56] = dcterm
;
853 int t0
, t1
, t2
, t3
, x0
, x1
, x2
, x3
;
854 STBI__IDCT_1D(t0
, t1
, t2
, t3
, x0
, x1
, x2
, x3
, d
[0], d
[8], d
[16], d
[24], d
[32], d
[40], d
[48], d
[56]);
855 // constants scaled things up by 1<<12; let's bring them back
856 // down, but keep 2 extra bits of precision
857 x0
+= 512; x1
+= 512; x2
+= 512; x3
+= 512;
870 for (i
= 0, v
= val
.ptr
; i
< 8; ++i
, v
+= 8, o
+= dst_stride
) {
871 // no fast case since the first 1D IDCT spread components out
872 int t0
, t1
, t2
, t3
, x0
, x1
, x2
, x3
;
873 STBI__IDCT_1D(t0
, t1
, t2
, t3
, x0
, x1
, x2
, x3
, v
[0], v
[1], v
[2], v
[3], v
[4], v
[5], v
[6], v
[7]);
874 // constants scaled things up by 1<<12, plus we had 1<<2 from first
875 // loop, plus horizontal and vertical each scale by sqrt(8) so together
876 // we've got an extra 1<<3, so 1<<17 total we need to remove.
877 // so we want to round that, which means adding 0.5*1<<17,
878 // aka 65536. Also, we'll end up with -128 to 127 that we want
879 // to encode as 0-255 by adding 128, so we'll add that before the shift
880 x0
+= 65536+(128<<17);
881 x1
+= 65536+(128<<17);
882 x2
+= 65536+(128<<17);
883 x3
+= 65536+(128<<17);
884 // tried computing the shifts into temps, or'ing the temps to see
885 // if any were out of range, but that was slower
886 o
[0] = stbi__clamp((x0
+t3
)>>17);
887 o
[7] = stbi__clamp((x0
-t3
)>>17);
888 o
[1] = stbi__clamp((x1
+t2
)>>17);
889 o
[6] = stbi__clamp((x1
-t2
)>>17);
890 o
[2] = stbi__clamp((x2
+t1
)>>17);
891 o
[5] = stbi__clamp((x2
-t1
)>>17);
892 o
[3] = stbi__clamp((x3
+t0
)>>17);
893 o
[4] = stbi__clamp((x3
-t0
)>>17);
898 ubyte stbi__clamp() (int x
) pure {
899 if (cast(uint)x
> 255) {
901 if (x
> 255) return 255;
905 // the above is adapted from stb_image
906 // ------------------------------------------------------------