1 ! Copyright (C) 2009 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs checksums checksums.crc32
4 combinators compression.inflate endian fry grouping images
5 images.loader io io.encodings.ascii
6 io.encodings.binary io.encodings.latin1 io.encodings.string
7 io.streams.byte-array io.streams.throwing kernel locals math
8 math.bitwise math.functions sequences sorting ;
13 "png" png-image ?register-image-class
15 TUPLE: icc-profile name data ;
17 TUPLE: itext keyword language translated-keyword text ;
21 width height bit-depth color-type compression-method
22 filter-method interlace-method icc-profile itexts ;
24 CONSTANT: filter-none 0
25 CONSTANT: filter-sub 1
27 CONSTANT: filter-average 3
28 CONSTANT: filter-paeth 4
32 CONSTANT: indexed-color 3
33 CONSTANT: greyscale-alpha 4
34 CONSTANT: truecolor-alpha 6
36 CONSTANT: interlace-none 0
37 CONSTANT: interlace-adam7 1
39 CONSTANT: starting-row { 0 0 4 0 2 0 1 }
40 CONSTANT: starting-col { 0 4 0 2 0 1 0 }
41 CONSTANT: row-increment { 8 8 8 4 4 2 2 }
42 CONSTANT: col-increment { 8 8 4 4 2 2 1 }
43 CONSTANT: block-height { 8 8 4 4 2 2 1 }
44 CONSTANT: block-width { 8 4 4 2 2 1 1 }
46 : <loading-png> ( -- image )
50 TUPLE: png-chunk type data ;
52 : <png-chunk> ( -- png-chunk )
53 png-chunk new ; inline
56 B{ 0x89 0x50 0x4e 0x47 0x0d 0x0a 0x1a 0x0a }
58 ERROR: bad-png-header header ;
60 : read-png-header ( -- )
61 8 read dup png-header sequence= [
67 : read-png-chunks ( loading-png -- loading-png )
70 read dup crc32 checksum-bytes
71 4 read = [ bad-checksum ] unless
73 [ ascii decode >>type ] [ B{ } like >>data ] bi*
74 [ over chunks>> push ]
75 [ type>> ] bi "IEND" =
76 [ read-png-chunks ] unless ;
78 : find-chunk ( loading-png string -- chunk )
79 [ chunks>> ] dip '[ type>> _ = ] find nip ;
81 : find-chunks ( loading-png string -- chunk )
82 [ chunks>> ] dip '[ type>> _ = ] filter ;
84 : read-png-string ( -- str )
85 { 0 } read-until drop latin1 decode ;
87 : parse-ihdr-chunk ( loading-png -- loading-png )
88 dup "IHDR" find-chunk data>> {
89 [ [ 0 4 ] dip subseq be> >>width ]
90 [ [ 4 8 ] dip subseq be> >>height ]
91 [ [ 8 ] dip nth >>bit-depth ]
92 [ [ 9 ] dip nth >>color-type ]
93 [ [ 10 ] dip nth >>compression-method ]
94 [ [ 11 ] dip nth >>filter-method ]
95 [ [ 12 ] dip nth >>interlace-method ]
98 : <icc-profile> ( byte-array -- icc-profile )
100 read-png-string read1 drop read-contents zlib-inflate
101 ] with-byte-reader icc-profile boa ;
103 : <itext> ( byte-array -- itext )
106 ! Skip compression flag and method
108 read-png-string read-png-string read-png-string
109 ] with-byte-reader itext boa ;
111 : parse-iccp-chunk ( loading-png -- loading-png )
112 dup "iCCP" find-chunk [
113 data>> <icc-profile> >>icc-profile
116 : parse-itext-chunks ( loading-png -- loading-png )
117 dup "iTXt" find-chunks [ data>> <itext> ] map >>itexts ;
119 : find-compressed-bytes ( loading-png -- bytes )
120 "IDAT" find-chunks [ data>> ] map concat ;
122 ERROR: unknown-color-type n ;
123 ERROR: unimplemented-color-type image ;
125 : inflate-data ( loading-png -- bytes )
126 find-compressed-bytes zlib-inflate ;
128 : png-components-per-pixel ( loading-png -- n )
132 { greyscale-alpha [ 2 ] }
133 { indexed-color [ 1 ] }
134 { truecolor-alpha [ 4 ] }
135 [ unknown-color-type ]
138 : png-group-width ( loading-png -- n )
139 ! 1 + is for the filter type, 1 byte preceding each line
140 [ [ png-components-per-pixel ] [ bit-depth>> ] bi * ]
141 [ width>> ] bi * 1 + ;
143 :: paeth ( a b c -- p )
144 a b + c - { a b c } [ [ - abs ] keep 2array ] with map
145 sort-keys first second ;
147 ERROR: bad-filter n ;
149 :: png-unfilter-line ( width prev curr filter -- curr' )
151 prev width tail-slice :> b
153 curr width tail-slice :> x
156 { filter-none [ drop ] }
157 { filter-sub [ [| n | n x nth n a nth + 256 wrap n x set-nth ] each ] }
158 { filter-up [ [| n | n x nth n b nth + 256 wrap n x set-nth ] each ] }
159 { filter-average [ [| n | n x nth n a nth n b nth + 2/ + 256 wrap n x set-nth ] each ] }
160 { filter-paeth [ [| n | n x nth n a nth n b nth n c nth paeth + 256 wrap n x set-nth ] each ] }
165 :: reverse-png-filter ( lines n -- byte-array )
166 lines dup first length 0 <array> prefix
167 [ n 1 - 0 <array> prepend ] map
172 [ [ 0 n 1 - ] dip set-nth ] tri
174 ] map B{ } concat-as ;
176 :: visit ( row col height width pixel image -- )
177 row image nth :> irow
178 pixel col irow set-nth ;
180 :: read-scanlines ( byte-reader loading-png width height -- array )
181 loading-png png-components-per-pixel :> #components
182 loading-png bit-depth>> :> bit-depth
184 #components width * :> count!
186 #components bit-depth * width * 8 math:align 8 /i :> stride
189 stride 1 + byte-reader stream-read
191 #components bit-depth 16 = [ 2 * ] when reverse-png-filter
193 ! Only read up to 8 bits at a time
199 bitstreams:<msb0-bit-reader> :> br
201 count [ depth br bitstreams:read ] B{ } replicate-as
202 8 br bitstreams:align
205 :: reverse-interlace-none ( bytes loading-png -- array )
206 bytes binary <byte-reader> :> br
207 loading-png width>> :> width
208 loading-png height>> :> height
209 br loading-png width height read-scanlines ;
211 :: adam7-subimage-height ( png-height pass -- subimage-height )
212 pass starting-row nth png-height >= [
216 pass block-height nth +
217 pass row-increment nth /i
220 :: adam7-subimage-width ( png-width pass -- subimage-width )
221 pass starting-col nth png-width >= [
225 pass block-width nth +
226 pass col-increment nth /i
229 :: read-adam7-subimage ( byte-reader loading-png pass -- lines )
230 loading-png height>> pass adam7-subimage-height :> height
231 loading-png width>> pass adam7-subimage-width :> width
233 height width * zero? [
236 byte-reader loading-png width height read-scanlines
239 :: reverse-interlace-adam7 ( byte-array loading-png -- byte-array )
240 byte-array binary <byte-reader> :> ba
241 loading-png height>> :> height
242 loading-png width>> :> width
243 loading-png bit-depth>> :> bit-depth
244 loading-png png-components-per-pixel :> #bytes!
245 width height * f <array> width <groups> :> image
256 ba loading-png pass read-adam7-subimage
260 pass starting-row nth row!
261 pass starting-col nth col!
263 [ row col f f ] dip image visit
265 col pass col-increment nth + col!
267 pass starting-col nth col!
268 row pass row-increment nth + row!
274 image concat B{ } concat-as ;
276 ERROR: unimplemented-interlace ;
278 : uncompress-bytes ( loading-png -- bitstream )
279 [ inflate-data ] [ ] [ interlace-method>> ] tri {
280 { interlace-none [ reverse-interlace-none ] }
281 { interlace-adam7 [ reverse-interlace-adam7 ] }
282 [ unimplemented-interlace ]
285 ERROR: unknown-component-type n ;
287 : png-component ( loading-png -- obj )
289 { 1 [ ubyte-components ] }
290 { 2 [ ubyte-components ] }
291 { 4 [ ubyte-components ] }
292 { 8 [ ubyte-components ] }
293 { 16 [ ushort-components ] }
294 [ unknown-component-type ]
297 : scale-factor ( n -- n' )
304 : scale-greyscale ( byte-array loading-png -- byte-array' )
307 { 16 [ 2 group [ swap ] assoc-map B{ } concat-as ] }
308 [ scale-factor '[ _ * ] B{ } map-as ]
311 : decode-greyscale ( loading-png -- byte-array )
312 [ uncompress-bytes ] keep scale-greyscale ;
314 : decode-greyscale-alpha ( loading-image -- byte-array )
315 [ uncompress-bytes ] [ bit-depth>> ] bi 16 = [
316 4 group [ first4 [ swap ] 2dip 4array ] map B{ } concat-as
319 ERROR: invalid-PLTE array ;
321 : verify-PLTE ( seq -- seq )
322 dup length 3 divisor? [ invalid-PLTE ] unless ;
324 : decode-indexed-color ( loading-image -- byte-array )
325 [ uncompress-bytes ] keep
326 "PLTE" find-chunk data>> verify-PLTE
327 3 group '[ _ nth ] { } map-as B{ } concat-as ;
329 ERROR: invalid-color-type/bit-depth loading-png ;
331 : validate-bit-depth ( loading-png seq -- loading-png )
332 [ dup bit-depth>> ] dip member?
333 [ invalid-color-type/bit-depth ] unless ;
335 : validate-greyscale ( loading-png -- loading-png )
336 { 1 2 4 8 16 } validate-bit-depth ;
338 : validate-truecolor ( loading-png -- loading-png )
339 { 8 16 } validate-bit-depth ;
341 : validate-indexed-color ( loading-png -- loading-png )
342 { 1 2 4 8 } validate-bit-depth ;
344 : validate-greyscale-alpha ( loading-png -- loading-png )
345 { 8 16 } validate-bit-depth ;
347 : validate-truecolor-alpha ( loading-png -- loading-png )
348 { 8 16 } validate-bit-depth ;
350 : loading-png>bitmap ( loading-png -- bytes component-order )
353 validate-greyscale decode-greyscale L
356 validate-truecolor uncompress-bytes RGB
359 validate-indexed-color decode-indexed-color RGB
362 validate-greyscale-alpha decode-greyscale-alpha LA
365 validate-truecolor-alpha uncompress-bytes RGBA
367 [ unknown-color-type ]
370 : loading-png>image ( loading-png -- image )
372 [ loading-png>bitmap [ >>bitmap ] [ >>component-order ] bi* ]
373 [ [ width>> ] [ height>> ] bi 2array >>dim ]
374 [ png-component >>component-type ]
377 : load-png ( stream -- loading-png )
387 ] with-input-stream ;
389 M: png-image stream>image*
390 drop load-png loading-png>image ;