]> gitweb.factorcode.org Git - factor.git/blob - basis/images/jpeg/jpeg.factor
Language change: tuple slot setter words with stack effect ( value object -- ) are...
[factor.git] / basis / images / jpeg / jpeg.factor
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 fry
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 io.streams.limited ;
10 IN: images.jpeg
11
12 QUALIFIED-WITH: bitstreams bs
13
14 SINGLETON: jpeg-image
15
16 TUPLE: loading-jpeg < image
17     { headers }
18     { bitstream }
19     { color-info initial: { f f f f } }
20     { quant-tables initial: { f f } }
21     { huff-tables initial: { f f f f } }
22     { components } ;
23
24 "jpg" jpeg-image register-image-class
25 "jpeg" jpeg-image register-image-class
26
27 <PRIVATE
28
29 : <loading-jpeg> ( headers bitstream -- image )
30     loading-jpeg new swap >>bitstream swap >>headers ;
31
32 SINGLETONS: SOF DHT DAC RST SOI EOI SOS DQT DNL DRI DHP EXP
33 APP JPG COM TEM RES ;
34
35 ! ISO/IEC 10918-1 Table B.1
36 :: >marker ( byte -- marker )
37     byte
38     {
39       { [ dup HEX: CC = ] [ { DAC } ] }
40       { [ dup HEX: C4 = ] [ { DHT } ] }
41       { [ dup HEX: C9 = ] [ { JPG } ] }
42       { [ dup -4 shift HEX: C = ] [ SOF byte 4 bits 2array ] }
43
44       { [ dup HEX: D8 = ] [ { SOI } ] }
45       { [ dup HEX: D9 = ] [ { EOI } ] }
46       { [ dup HEX: DA = ] [ { SOS } ] }
47       { [ dup HEX: DB = ] [ { DQT } ] }
48       { [ dup HEX: DC = ] [ { DNL } ] }
49       { [ dup HEX: DD = ] [ { DRI } ] }
50       { [ dup HEX: DE = ] [ { DHP } ] }
51       { [ dup HEX: DF = ] [ { EXP } ] }
52       { [ dup -4 shift HEX: D = ] [ RST byte 4 bits 2array ] }
53
54       { [ dup -4 shift HEX: E = ] [ APP byte 4 bits 2array ] }
55       { [ dup HEX: FE = ] [ { COM } ] }
56       { [ dup -4 shift HEX: F = ] [ JPG byte 4 bits 2array ] }
57
58       { [ dup HEX: 01 = ] [ { TEM } ] }
59       [ { RES } ]
60     }
61     cond nip ;
62
63 TUPLE: jpeg-chunk length type data ;
64
65 : <jpeg-chunk> ( type length data -- jpeg-chunk )
66     jpeg-chunk new
67         swap >>data
68         swap >>length
69         swap >>type ;
70
71 TUPLE: jpeg-color-info
72     h v quant-table dc-huff-table ac-huff-table { diff initial: 0 } id ;
73
74 : <jpeg-color-info> ( h v quant-table -- jpeg-color-info )
75     jpeg-color-info new
76         swap >>quant-table
77         swap >>v
78         swap >>h ;
79
80 : jpeg> ( -- jpeg-image ) jpeg-image get ;
81
82 : apply-diff ( dc color -- dc' )
83     [ diff>> + dup ] [ diff<< ] bi ;
84
85 : fetch-tables ( component -- )
86     [ [ jpeg> quant-tables>> nth ] change-quant-table drop ]
87     [ [ jpeg> huff-tables>> nth ] change-dc-huff-table drop ]
88     [ [ 2 + jpeg> huff-tables>> nth ] change-ac-huff-table drop ] tri ;
89
90 : read4/4 ( -- a b ) read1 16 /mod ;
91
92 ! headers
93
94 : decode-frame ( header -- )
95     data>>
96     binary
97     [
98         read1 8 assert=
99         2 read be>
100         2 read be>
101         swap 2array jpeg> dim<<
102         read1
103         [
104             read1 read4/4 read1 <jpeg-color-info>
105             swap [ >>id ] keep jpeg> color-info>> set-nth
106         ] times
107     ] with-byte-reader ;
108
109 : decode-quant-table ( chunk -- )
110     dup data>>
111     binary
112     [
113         length>>
114         2 - 65 /
115         [
116             read4/4 [ 0 assert= ] dip
117             64 read
118             swap jpeg> quant-tables>> set-nth
119         ] times
120     ] with-byte-reader ;
121
122 : decode-huff-table ( chunk -- )
123     data>> [ binary <byte-reader> ] [ length ] bi
124     stream-throws limit
125     [   
126         [ input-stream get [ count>> ] [ limit>> ] bi < ]
127         [
128             read4/4 swap 2 * +
129             16 read
130             dup [ ] [ + ] map-reduce read
131             binary [ [ read [ B{ } ] unless* ] { } map-as ] with-byte-reader
132             swap jpeg> huff-tables>> set-nth
133         ] while
134     ] with-input-stream* ;
135
136 : decode-scan ( chunk -- )
137     data>>
138     binary
139     [
140         read1 iota
141         [   drop
142             read1 jpeg> color-info>> nth clone
143             read1 16 /mod [ >>dc-huff-table ] [ >>ac-huff-table ] bi*
144         ] map jpeg> components<<
145         read1 0 assert=
146         read1 63 assert=
147         read1 16 /mod [ 0 assert= ] bi@
148     ] with-byte-reader ;
149
150 : singleton-first ( seq -- elt )
151     [ length 1 assert= ] [ first ] bi ;
152
153 ERROR: not-a-baseline-jpeg-image ;
154
155 : baseline-parse ( -- )
156     jpeg> headers>> [ type>> { SOF 0 } = ] any? [ not-a-baseline-jpeg-image ] unless
157     jpeg> headers>>
158     {
159         [ [ type>> { SOF 0 } = ] filter singleton-first decode-frame ]
160         [ [ type>> { DQT } = ] filter [ decode-quant-table ] each ]
161         [ [ type>> { DHT } = ] filter [ decode-huff-table ] each ]
162         [ [ type>> { SOS } = ] filter singleton-first decode-scan ]
163     } cleave ;
164
165 : parse-marker ( -- marker )
166     read1 HEX: FF assert=
167     read1 >marker ;
168
169 : parse-headers ( -- chunks )
170     [ parse-marker dup { SOS } = not ]
171     [
172         2 read be>
173         dup 2 - read <jpeg-chunk>
174     ] [ produce ] keep dip swap suffix ;
175
176 MEMO: zig-zag ( -- zz )
177     {
178         {  0  1  5  6 14 15 27 28 }
179         {  2  4  7 13 16 26 29 42 }
180         {  3  8 12 17 25 30 41 43 }
181         {  9 11 18 24 31 40 44 53 }
182         { 10 19 23 32 39 45 52 54 }
183         { 20 22 33 38 46 51 55 60 }
184         { 21 34 37 47 50 56 59 61 }
185         { 35 36 48 49 57 58 62 63 }
186     } flatten ;
187
188 MEMO: yuv>bgr-matrix ( -- m )
189     {
190         { 1  2.03211  0       }
191         { 1 -0.39465 -0.58060 }
192         { 1  0        1.13983 }
193     } ;
194
195 : wave ( x u -- n ) swap 2 * 1 + * pi * 16 / cos ;
196
197 :: dct-vect ( u v -- basis )
198     { 8 8 } coord-matrix [ { u v } [ wave ] 2map product ] map^2
199     1 u v [ 0 = [ 2 sqrt / ] when ] bi@ 4 / m*n ;
200
201 MEMO: dct-matrix ( -- m ) 64 iota [ 8 /mod dct-vect flatten ] map ;
202
203 : mb-dim ( component -- dim )  [ h>> ] [ v>> ] bi 2array ;
204
205 ! : blocks ( component -- seq )
206 !    mb-dim ! coord-matrix flip concat [ [ { 2 2 } v* ] [ v+ ] bi* ] with map ;
207
208 : all-macroblocks ( quot: ( mb -- ) -- )
209     [
210         jpeg>
211         [ dim>> 8 v/n ]
212         [ color-info>> sift { 0 0 } [ mb-dim vmax ] reduce v/ ] bi
213         [ ceiling ] map
214         coord-matrix flip concat
215     ]
216     [ each ] bi* ; inline
217
218 : reverse-zigzag ( b -- b' ) zig-zag swap [ nth ] curry map ;
219
220 : idct-factor ( b -- b' ) dct-matrix v.m ;
221
222 USE: math.blas.vectors
223 USE: math.blas.matrices
224
225 MEMO: dct-matrix-blas ( -- m ) dct-matrix >float-blas-matrix ;
226 : V.M ( x A -- x.A ) Mtranspose swap M.V ;
227 : idct-blas ( b -- b' ) >float-blas-vector dct-matrix-blas V.M ;
228
229 : idct ( b -- b' ) idct-factor ;
230
231 :: draw-block ( block x,y color-id jpeg-image -- )
232     block dup length>> sqrt >fixnum group flip
233     dup matrix-dim coord-matrix flip
234     [
235         [ '[ _ [ second ] [ first ] bi ] dip nth nth ]
236         [ x,y v+ color-id jpeg-image draw-color ] bi
237     ] with each^2 ;
238
239 : sign-extend ( bits v -- v' )
240     swap [ ] [ 1 - 2^ < ] 2bi
241     [ -1 swap shift 1 + + ] [ drop ] if ;
242
243 : read1-jpeg-dc ( decoder -- dc )
244     [ read1-huff dup ] [ bs>> bs:read ] bi sign-extend ;
245
246 : read1-jpeg-ac ( decoder -- run/ac )
247     [ read1-huff 16 /mod dup ] [ bs>> bs:read ] bi sign-extend 2array ;
248
249 :: decode-block ( color -- pixels )
250     color dc-huff-table>> read1-jpeg-dc color apply-diff
251     64 0 <array> :> coefs
252     0 coefs set-nth
253     0 :> k!
254     [
255         color ac-huff-table>> read1-jpeg-ac
256         [ first 1 + k + k! ] [ second k coefs set-nth ] [ ] tri
257         { 0 0 } = not
258         k 63 < and
259     ] loop
260     coefs color quant-table>> v*
261     reverse-zigzag idct ;
262     
263 :: draw-macroblock-yuv420 ( mb blocks -- )
264     mb { 16 16 } v* :> pos
265     0 blocks nth pos { 0 0 } v+ 0 jpeg> draw-block
266     1 blocks nth pos { 8 0 } v+ 0 jpeg> draw-block
267     2 blocks nth pos { 0 8 } v+ 0 jpeg> draw-block
268     3 blocks nth pos { 8 8 } v+ 0 jpeg> draw-block
269     4 blocks nth 8 group 2 matrix-zoom concat pos 1 jpeg> draw-block
270     5 blocks nth 8 group 2 matrix-zoom concat pos 2 jpeg> draw-block ;
271     
272 :: draw-macroblock-yuv444 ( mb blocks -- )
273     mb { 8 8 } v* :> pos
274     3 iota [ [ blocks nth pos ] [ jpeg> draw-block ] bi ] each ;
275
276 :: draw-macroblock-y ( mb blocks -- )
277     mb { 8 8 } v* :> pos
278     0 blocks nth pos 0 jpeg> draw-block
279     64 0 <array> pos 1 jpeg> draw-block
280     64 0 <array> pos 2 jpeg> draw-block ;
281  
282     ! %fixme: color hack
283  !   color h>> 2 =
284  !   [ 8 group 2 matrix-zoom concat ] unless
285  !   pos { 8 8 } v* color jpeg> draw-block ;
286
287 : decode-macroblock ( -- blocks )
288     jpeg> components>>
289     [
290         [ mb-dim first2 * ]
291         [ [ decode-block ] curry replicate ] bi
292     ] map concat ;
293
294 : cleanup-bitstream ( bytes -- bytes' )
295     binary [
296         [
297             { HEX: FF } read-until
298             read1 [ HEX: 00 = and ] keep swap
299         ]
300         [ drop ] produce
301         swap >marker {  EOI } assert=
302         swap suffix
303         { HEX: FF } join
304     ] with-byte-reader ;
305
306 : setup-bitmap ( image -- )
307     dup dim>> 16 v/n [ ceiling ] map 16 v*n >>dim
308     BGR >>component-order
309     ubyte-components >>component-type
310     f >>upside-down?
311     dup dim>> first2 * 3 * 0 <array> >>bitmap
312     drop ;
313
314 ERROR: unsupported-colorspace ;
315 SINGLETONS: YUV420 YUV444 Y MAGIC! ;
316
317 :: detect-colorspace ( jpeg-image -- csp )
318     jpeg-image color-info>> sift :> colors
319     MAGIC!
320     colors length 1 = [ drop Y ] when
321     colors length 3 =
322     [
323         colors [ mb-dim { 1 1 } = ] all?
324         [ drop YUV444 ] when
325
326         colors unclip
327         [ [ mb-dim { 1 1 } = ] all? ]
328         [ mb-dim { 2 2 } =  ] bi* and
329         [ drop YUV420 ] when
330     ] when ;
331     
332 ! this eats ~50% cpu time
333 : draw-macroblocks ( mbs -- )
334     jpeg> detect-colorspace
335     {
336         { YUV420 [ [ first2 draw-macroblock-yuv420 ] each ] }
337         { YUV444 [ [ first2 draw-macroblock-yuv444 ] each ] }
338         { Y      [ [ first2 draw-macroblock-y ] each ] }
339         [ unsupported-colorspace ]
340     } case ;
341
342 ! this eats ~25% cpu time
343 : color-transform ( yuv -- rgb )
344     { 128 0 0 } v+ yuv>bgr-matrix swap m.v
345     [ 0 max 255 min >fixnum ] map ;
346
347 : baseline-decompress ( -- )
348     jpeg> bitstream>> cleanup-bitstream { 255 255 255 255 } append
349     >byte-array bs:<msb0-bit-reader> jpeg> bitstream<<
350     jpeg> 
351     [ bitstream>> ] 
352     [ [ [ <huffman-decoder> ] with map ] change-huff-tables drop ] bi
353     jpeg> components>> [ fetch-tables ] each
354     [ decode-macroblock 2array ] collector 
355     [ all-macroblocks ] dip
356     jpeg> setup-bitmap draw-macroblocks 
357     jpeg> bitmap>> 3 <groups> [ color-transform ] map! drop
358     jpeg> [ >byte-array ] change-bitmap drop ;
359
360 ERROR: not-a-jpeg-image ;
361
362 : loading-jpeg>image ( loading-jpeg -- image )
363     dup jpeg-image [
364         baseline-parse
365         baseline-decompress
366     ] with-variable ;
367
368 : load-jpeg ( stream -- loading-jpeg )
369     [
370         parse-marker { SOI } = [ not-a-jpeg-image ] unless
371         parse-headers
372         unlimited-input contents <loading-jpeg>
373     ] with-input-stream ;
374
375 PRIVATE>
376
377 M: jpeg-image stream>image ( stream jpeg-image -- bitmap )
378     drop load-jpeg loading-jpeg>image ;