1 ! Copyright (C) 2009 Marc Fauconneau.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays byte-arrays combinators
4 grouping compression.huffman images
5 images.processing io io.binary io.encodings.binary io.files
6 io.streams.byte-array kernel locals math math.bitwise
7 math.constants math.functions math.matrices math.order
8 math.ranges math.vectors memoize multiline namespaces
9 sequences sequences.deep images.loader ;
12 QUALIFIED-WITH: bitstreams bs
14 TUPLE: jpeg-image < image
17 { color-info initial: { f f f f } }
18 { quant-tables initial: { f f } }
19 { huff-tables initial: { f f f f } }
22 "jpg" jpeg-image register-image-class
23 "jpeg" jpeg-image register-image-class
27 : <jpeg-image> ( headers bitstream -- image )
28 jpeg-image new swap >>bitstream swap >>headers ;
30 SINGLETONS: SOF DHT DAC RST SOI EOI SOS DQT DNL DRI DHP EXP
33 ! ISO/IEC 10918-1 Table B.1
34 :: >marker ( byte -- marker )
37 { [ dup HEX: CC = ] [ { DAC } ] }
38 { [ dup HEX: C4 = ] [ { DHT } ] }
39 { [ dup HEX: C9 = ] [ { JPG } ] }
40 { [ dup -4 shift HEX: C = ] [ SOF byte 4 bits 2array ] }
42 { [ dup HEX: D8 = ] [ { SOI } ] }
43 { [ dup HEX: D9 = ] [ { EOI } ] }
44 { [ dup HEX: DA = ] [ { SOS } ] }
45 { [ dup HEX: DB = ] [ { DQT } ] }
46 { [ dup HEX: DC = ] [ { DNL } ] }
47 { [ dup HEX: DD = ] [ { DRI } ] }
48 { [ dup HEX: DE = ] [ { DHP } ] }
49 { [ dup HEX: DF = ] [ { EXP } ] }
50 { [ dup -4 shift HEX: D = ] [ RST byte 4 bits 2array ] }
52 { [ dup -4 shift HEX: E = ] [ APP byte 4 bits 2array ] }
53 { [ dup HEX: FE = ] [ { COM } ] }
54 { [ dup -4 shift HEX: F = ] [ JPG byte 4 bits 2array ] }
56 { [ dup HEX: 01 = ] [ { TEM } ] }
61 TUPLE: jpeg-chunk length type data ;
63 : <jpeg-chunk> ( type length data -- jpeg-chunk )
69 TUPLE: jpeg-color-info
70 h v quant-table dc-huff-table ac-huff-table { diff initial: 0 } id ;
72 : <jpeg-color-info> ( h v quant-table -- jpeg-color-info )
78 : jpeg> ( -- jpeg-image ) jpeg-image get ;
80 : apply-diff ( dc color -- dc' )
81 [ diff>> + dup ] [ (>>diff) ] bi ;
83 : fetch-tables ( component -- )
84 [ [ jpeg> quant-tables>> nth ] change-quant-table drop ]
85 [ [ jpeg> huff-tables>> nth ] change-dc-huff-table drop ]
86 [ [ 2 + jpeg> huff-tables>> nth ] change-ac-huff-table drop ] tri ;
88 : read4/4 ( -- a b ) read1 16 /mod ;
92 : decode-frame ( header -- )
99 swap 2array jpeg> (>>dim)
102 read1 read4/4 read1 <jpeg-color-info>
103 swap [ >>id ] keep jpeg> color-info>> set-nth
107 : decode-quant-table ( chunk -- )
114 read4/4 [ 0 assert= ] dip
116 swap jpeg> quant-tables>> set-nth
120 : decode-huff-table ( chunk -- )
124 1 ! %fixme: Should handle multiple tables at once
128 dup [ ] [ + ] map-reduce read
129 binary [ [ read [ B{ } ] unless* ] { } map-as ] with-byte-reader
130 swap jpeg> huff-tables>> set-nth
134 : decode-scan ( chunk -- )
140 read1 jpeg> color-info>> nth clone
141 read1 16 /mod [ >>dc-huff-table ] [ >>ac-huff-table ] bi*
142 ] map jpeg> (>>components)
145 read1 16 /mod [ 0 assert= ] bi@
148 : singleton-first ( seq -- elt )
149 [ length 1 assert= ] [ first ] bi ;
151 : baseline-parse ( -- )
154 [ [ type>> { SOF 0 } = ] filter singleton-first decode-frame ]
155 [ [ type>> { DQT } = ] filter [ decode-quant-table ] each ]
156 [ [ type>> { DHT } = ] filter [ decode-huff-table ] each ]
157 [ [ type>> { SOS } = ] filter singleton-first decode-scan ]
160 : parse-marker ( -- marker )
161 read1 HEX: FF assert=
164 : parse-headers ( -- chunks )
165 [ parse-marker dup { SOS } = not ]
168 dup 2 - read <jpeg-chunk>
169 ] [ produce ] keep dip swap suffix ;
171 MEMO: zig-zag ( -- zz )
173 { 0 1 5 6 14 15 27 28 }
174 { 2 4 7 13 16 26 29 42 }
175 { 3 8 12 17 25 30 41 43 }
176 { 9 11 18 24 31 40 44 53 }
177 { 10 19 23 32 39 45 52 54 }
178 { 20 22 33 38 46 51 55 60 }
179 { 21 34 37 47 50 56 59 61 }
180 { 35 36 48 49 57 58 62 63 }
183 MEMO: yuv>bgr-matrix ( -- m )
186 { 1 -0.39465 -0.58060 }
190 : wave ( x u -- n ) swap 2 * 1 + * pi * 16 / cos ;
192 :: dct-vect ( u v -- basis )
193 { 8 8 } coord-matrix [ { u v } [ wave ] 2map product ] map^2
194 1 u v [ 0 = [ 2 sqrt / ] when ] bi@ 4 / m*n ;
196 MEMO: dct-matrix ( -- m ) 64 [0,b) [ 8 /mod dct-vect flatten ] map ;
198 : mb-dim ( component -- dim ) [ h>> ] [ v>> ] bi 2array ;
200 ! : blocks ( component -- seq )
201 ! mb-dim ! coord-matrix flip concat [ [ { 2 2 } v* ] [ v+ ] bi* ] with map ;
203 : all-macroblocks ( quot: ( mb -- ) -- )
207 [ color-info>> sift { 0 0 } [ mb-dim vmax ] reduce v/ ] bi
209 coord-matrix flip concat
211 [ each ] bi* ; inline
213 : reverse-zigzag ( b -- b' ) zig-zag swap [ nth ] curry map ;
215 : idct-factor ( b -- b' ) dct-matrix v.m ;
217 USE: math.blas.vectors
218 USE: math.blas.matrices
220 MEMO: dct-matrix-blas ( -- m ) dct-matrix >float-blas-matrix ;
221 : V.M ( x A -- x.A ) Mtranspose swap M.V ;
222 : idct-blas ( b -- b' ) >float-blas-vector dct-matrix-blas V.M ;
224 : idct ( b -- b' ) idct-blas ;
226 :: draw-block ( block x,y color-id jpeg-image -- )
227 block dup length>> sqrt >fixnum group flip
228 dup matrix-dim coord-matrix flip
230 [ first2 spin nth nth ]
231 [ x,y v+ color-id jpeg-image draw-color ] bi
234 : sign-extend ( bits v -- v' )
235 swap [ ] [ 1 - 2^ < ] 2bi
236 [ -1 swap shift 1 + + ] [ drop ] if ;
238 : read1-jpeg-dc ( decoder -- dc )
239 [ read1-huff dup ] [ bs>> bs:read ] bi sign-extend ;
241 : read1-jpeg-ac ( decoder -- run/ac )
242 [ read1-huff 16 /mod dup ] [ bs>> bs:read ] bi sign-extend 2array ;
244 :: decode-block ( color -- pixels )
245 color dc-huff-table>> read1-jpeg-dc color apply-diff
246 64 0 <array> :> coefs
250 color ac-huff-table>> read1-jpeg-ac
251 [ first 1 + k + k! ] [ second k coefs set-nth ] [ ] tri
255 coefs color quant-table>> v*
256 reverse-zigzag idct ;
258 :: draw-macroblock-yuv420 ( mb blocks -- )
259 mb { 16 16 } v* :> pos
260 0 blocks nth pos { 0 0 } v+ 0 jpeg> draw-block
261 1 blocks nth pos { 8 0 } v+ 0 jpeg> draw-block
262 2 blocks nth pos { 0 8 } v+ 0 jpeg> draw-block
263 3 blocks nth pos { 8 8 } v+ 0 jpeg> draw-block
264 4 blocks nth 8 group 2 matrix-zoom concat pos 1 jpeg> draw-block
265 5 blocks nth 8 group 2 matrix-zoom concat pos 2 jpeg> draw-block ;
267 :: draw-macroblock-yuv444 ( mb blocks -- )
269 3 iota [ [ blocks nth pos ] [ jpeg> draw-block ] bi ] each ;
271 :: draw-macroblock-y ( mb blocks -- )
273 0 blocks nth pos 0 jpeg> draw-block
274 64 0 <array> pos 1 jpeg> draw-block
275 64 0 <array> pos 2 jpeg> draw-block ;
279 ! [ 8 group 2 matrix-zoom concat ] unless
280 ! pos { 8 8 } v* color jpeg> draw-block ;
282 : decode-macroblock ( -- blocks )
285 [ mb-dim first2 * iota ]
286 [ [ decode-block ] curry replicate ] bi
289 : cleanup-bitstream ( bytes -- bytes' )
292 { HEX: FF } read-until
293 read1 tuck HEX: 00 = and
296 swap >marker { EOI } assert=
301 : setup-bitmap ( image -- )
302 dup dim>> 16 v/n [ ceiling ] map 16 v*n >>dim
303 BGR >>component-order
304 ubyte-components >>component-type
306 dup dim>> first2 * 3 * 0 <array> >>bitmap
309 ERROR: unsupported-colorspace ;
310 SINGLETONS: YUV420 YUV444 Y MAGIC! ;
312 :: detect-colorspace ( jpeg-image -- csp )
313 jpeg-image color-info>> sift :> colors
315 colors length 1 = [ drop Y ] when
318 colors [ mb-dim { 1 1 } = ] all?
322 [ [ mb-dim { 1 1 } = ] all? ]
323 [ mb-dim { 2 2 } = ] bi* and
327 ! this eats ~50% cpu time
328 : draw-macroblocks ( mbs -- )
329 jpeg> detect-colorspace
331 { YUV420 [ [ first2 draw-macroblock-yuv420 ] each ] }
332 { YUV444 [ [ first2 draw-macroblock-yuv444 ] each ] }
333 { Y [ [ first2 draw-macroblock-y ] each ] }
334 [ unsupported-colorspace ]
337 ! this eats ~25% cpu time
338 : color-transform ( yuv -- rgb )
339 { 128 0 0 } v+ yuv>bgr-matrix swap m.v
340 [ 0 max 255 min >fixnum ] map ;
342 : baseline-decompress ( -- )
343 jpeg> bitstream>> cleanup-bitstream { 255 255 255 255 } append
344 >byte-array bs:<msb0-bit-reader> jpeg> (>>bitstream)
347 [ [ [ <huffman-decoder> ] with map ] change-huff-tables drop ] bi
348 jpeg> components>> [ fetch-tables ] each
349 [ decode-macroblock 2array ] accumulator
350 [ all-macroblocks ] dip
351 jpeg> setup-bitmap draw-macroblocks
352 jpeg> bitmap>> 3 <groups> [ color-transform ] change-each
353 jpeg> [ >byte-array ] change-bitmap drop ;
355 ERROR: not-a-jpeg-image ;
359 M: jpeg-image stream>image ( stream jpeg-image -- bitmap )
361 parse-marker { SOI } = [ not-a-jpeg-image ] unless
363 contents <jpeg-image>