1 ! Copyright (C) 2009 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays checksums checksums.crc32 combinators
4 compression.inflate fry grouping images images.loader io
5 io.binary io.encodings.ascii io.encodings.string kernel locals
6 math math.bitwise math.ranges sequences sorting assocs
7 math.functions math.order byte-arrays ;
8 QUALIFIED-WITH: bitstreams bs
12 "png" png-image register-image-class
16 width height bit-depth color-type compression-method
17 filter-method interlace-method uncompressed ;
19 CONSTANT: filter-none 0
20 CONSTANT: filter-sub 1
22 CONSTANT: filter-average 3
23 CONSTANT: filter-paeth 4
27 CONSTANT: indexed-color 3
28 CONSTANT: greyscale-alpha 4
29 CONSTANT: truecolor-alpha 6
31 CONSTANT: interlace-none 0
32 CONSTANT: interlace-adam7 1
34 CONSTANT: starting-row { 0 0 4 0 2 0 1 }
35 CONSTANT: starting-col { 0 4 0 2 0 1 0 }
36 CONSTANT: row-increment { 8 8 8 4 4 2 2 }
37 CONSTANT: col-increment { 8 8 4 4 2 2 1 }
38 CONSTANT: block-height { 8 8 4 4 2 2 1 }
39 CONSTANT: block-width { 8 4 4 2 2 1 1 }
41 : <loading-png> ( -- image )
45 TUPLE: png-chunk length type data ;
47 : <png-chunk> ( -- png-chunk )
48 png-chunk new ; inline
51 B{ HEX: 89 HEX: 50 HEX: 4e HEX: 47 HEX: 0d HEX: 0a HEX: 1a HEX: 0a }
53 ERROR: bad-png-header header ;
55 : read-png-header ( -- )
56 8 read dup png-header sequence= [
62 : read-png-chunks ( loading-png -- loading-png )
64 4 read be> [ >>length ] [ 4 + ] bi
65 read dup crc32 checksum-bytes
66 4 read = [ bad-checksum ] unless
68 [ ascii decode >>type ] [ B{ } like >>data ] bi*
69 [ over chunks>> push ]
70 [ type>> ] bi "IEND" =
71 [ read-png-chunks ] unless ;
73 : find-chunk ( loading-png string -- chunk )
74 [ chunks>> ] dip '[ type>> _ = ] find nip ;
76 : find-chunks ( loading-png string -- chunk )
77 [ chunks>> ] dip '[ type>> _ = ] filter ;
79 : parse-ihdr-chunk ( loading-png -- loading-png )
80 dup "IHDR" find-chunk data>> {
81 [ [ 0 4 ] dip subseq be> >>width ]
82 [ [ 4 8 ] dip subseq be> >>height ]
83 [ [ 8 ] dip nth >>bit-depth ]
84 [ [ 9 ] dip nth >>color-type ]
85 [ [ 10 ] dip nth >>compression-method ]
86 [ [ 11 ] dip nth >>filter-method ]
87 [ [ 12 ] dip nth >>interlace-method ]
90 : find-compressed-bytes ( loading-png -- bytes )
91 "IDAT" find-chunks [ data>> ] map concat ;
93 ERROR: unknown-color-type n ;
94 ERROR: unimplemented-color-type image ;
96 : inflate-data ( loading-png -- bytes )
97 find-compressed-bytes zlib-inflate ;
99 : png-components-per-pixel ( loading-png -- n )
103 { greyscale-alpha [ 2 ] }
104 { indexed-color [ 1 ] }
105 { truecolor-alpha [ 4 ] }
106 [ unknown-color-type ]
109 : png-group-width ( loading-png -- n )
110 ! 1 + is for the filter type, 1 byte preceding each line
111 [ [ png-components-per-pixel ] [ bit-depth>> ] bi * ]
112 [ width>> ] bi * 1 + ;
114 :: paeth ( a b c -- p )
115 a b + c - { a b c } [ [ - abs ] keep 2array ] with map
116 sort-keys first second ;
118 :: png-unfilter-line ( width prev curr filter -- curr' )
120 prev width tail-slice :> b
122 curr width tail-slice :> x
125 { filter-none [ drop ] }
126 { filter-sub [ [| n | n x nth n a nth + 256 wrap n x set-nth ] each ] }
127 { filter-up [ [| n | n x nth n b nth + 256 wrap n x set-nth ] each ] }
128 { filter-average [ [| n | n x nth n a nth n b nth + 2/ + 256 wrap n x set-nth ] each ] }
129 { filter-paeth [ [| n | n x nth n a nth n b nth n c nth paeth + 256 wrap n x set-nth ] each ] }
133 :: reverse-png-filter ( lines n -- byte-array )
134 lines dup first length 0 <array> prefix
135 [ n 1 - 0 <array> prepend ] map
140 [ [ 0 n 1 - ] dip set-nth ] tri
142 ] map B{ } concat-as ;
144 :: visit ( row col height width pixel image -- )
145 row image nth :> irow
146 pixel col irow set-nth ;
148 ERROR: bad-filter n ;
150 :: reverse-interlace-none ( byte-array loading-png -- array )
151 byte-array bs:<msb0-bit-reader> :> bs
152 loading-png width>> :> width
153 loading-png height>> :> height
154 loading-png png-components-per-pixel :> #components
155 loading-png bit-depth>> :> bit-depth
157 #components width * :> count!
159 ! Only read up to 8 bits at a time
166 8 bs bs:read dup 0 4 between? [ bad-filter ] unless
167 count [ depth bs bs:read ] replicate swap prefix
170 #components bit-depth 16 = [ 2 * ] when reverse-png-filter ;
172 :: reverse-interlace-adam7 ( byte-array loading-png -- byte-array )
173 byte-array bs:<msb0-bit-reader> :> bs
174 loading-png height>> :> height
175 loading-png width>> :> width
176 loading-png bit-depth>> :> bit-depth
177 loading-png png-components-per-pixel :> #bytes
178 width height #bytes * * <byte-array> width <sliced-groups> :> image
185 pass starting-row nth row!
189 pass starting-col nth col!
196 pass block-height nth
206 col pass col-increment nth + col!
208 row pass row-increment nth + row!
214 [ 2 >be ] map B{ } concat-as
219 ERROR: unimplemented-interlace ;
221 : uncompress-bytes ( loading-png -- bitstream )
222 [ inflate-data ] [ ] [ interlace-method>> ] tri {
223 { interlace-none [ reverse-interlace-none ] }
224 { interlace-adam7 [ "adam7 is broken" throw reverse-interlace-adam7 ] }
225 [ unimplemented-interlace ]
228 ERROR: unknown-component-type n ;
230 : png-component ( loading-png -- obj )
232 { 1 [ ubyte-components ] }
233 { 2 [ ubyte-components ] }
234 { 4 [ ubyte-components ] }
235 { 8 [ ubyte-components ] }
236 { 16 [ ushort-components ] }
237 [ unknown-component-type ]
240 : scale-factor ( n -- n' )
247 : scale-greyscale ( byte-array loading-png -- byte-array' )
250 { 16 [ 2 group [ swap ] assoc-map B{ } concat-as ] }
251 [ scale-factor '[ _ * ] B{ } map-as ]
254 : decode-greyscale ( loading-png -- byte-array )
255 [ uncompress-bytes ] keep scale-greyscale ;
257 : decode-greyscale-alpha ( loading-image -- byte-array )
258 [ uncompress-bytes ] [ bit-depth>> ] bi 16 = [
259 4 group [ first4 [ swap ] 2dip 4array ] map B{ } concat-as
262 ERROR: invalid-PLTE array ;
264 : verify-PLTE ( seq -- seq )
265 dup length 3 divisor? [ invalid-PLTE ] unless ;
267 : decode-indexed-color ( loading-image -- byte-array )
268 [ uncompress-bytes ] keep
269 "PLTE" find-chunk data>> verify-PLTE
270 3 group '[ _ nth ] { } map-as B{ } concat-as ;
272 ERROR: invalid-color-type/bit-depth loading-png ;
274 : validate-bit-depth ( loading-png seq -- loading-png )
275 [ dup bit-depth>> ] dip member?
276 [ invalid-color-type/bit-depth ] unless ;
278 : validate-greyscale ( loading-png -- loading-png )
279 { 1 2 4 8 16 } validate-bit-depth ;
281 : validate-truecolor ( loading-png -- loading-png )
282 { 8 16 } validate-bit-depth ;
284 : validate-indexed-color ( loading-png -- loading-png )
285 { 1 2 4 8 } validate-bit-depth ;
287 : validate-greyscale-alpha ( loading-png -- loading-png )
288 { 8 16 } validate-bit-depth ;
290 : validate-truecolor-alpha ( loading-png -- loading-png )
291 { 8 16 } validate-bit-depth ;
293 : pad-bitmap ( image -- image )
294 dup dim>> first 4 divisor? [
295 dup [ bytes-per-pixel ]
297 [ dim>> first 4 mod ] tri
298 '[ _ group [ _ 0 <array> append ] map B{ } concat-as ] change-bitmap
301 : loading-png>bitmap ( loading-png -- bytes component-order )
304 validate-greyscale decode-greyscale L
307 validate-truecolor uncompress-bytes RGB
310 validate-indexed-color decode-indexed-color RGB
313 validate-greyscale-alpha decode-greyscale-alpha LA
316 validate-truecolor-alpha uncompress-bytes RGBA
318 [ unknown-color-type ]
321 : loading-png>image ( loading-png -- image )
323 [ loading-png>bitmap [ >>bitmap ] [ >>component-order ] bi* ]
324 [ [ width>> ] [ height>> ] bi 2array >>dim ]
325 [ png-component >>component-type ]
326 } cleave pad-bitmap ;
328 : load-png ( stream -- loading-png )
334 ] with-input-stream ;
336 M: png-image stream>image
337 drop load-png loading-png>image ;