From 8b5871e9d8abdbcdbad4db3892dec09e53213ca0 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Wed, 23 Sep 2009 12:06:25 -0400 Subject: [PATCH 01/19] images.gif: fixed image-descriptor parse bug --- extra/images/gif/gif.factor | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index 9e1bc347b2..cbe7fa5f3a 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -42,7 +42,7 @@ packed delay-time color-index block-terminator ; TUPLE: image-descriptor -separator left top width height flags ; +left top width height flags lzw-min-code-size ; TUPLE: plain-text-extension introducer label block-size text-grid-left text-grid-top text-grid-width @@ -92,12 +92,12 @@ M: input-port stream-peek1 : read-image-descriptor ( -- image-descriptor ) \ image-descriptor new - 1 read le> >>separator 2 read le> >>left 2 read le> >>top 2 read le> >>width 2 read le> >>height - 1 read le> >>flags ; + 1 read le> >>flags + 1 read le> >>lzw-min-code-size ; : read-graphic-control-extension ( -- graphic-control-extension ) \ graphics-control-extension new From 3cbf48cae7305054163fc3174a279d43820223fb Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Wed, 23 Sep 2009 12:06:49 -0400 Subject: [PATCH 02/19] images.gif: added unit tests --- extra/images/gif/gif-tests.factor | 29 ++++++++++++++++++ extra/images/testing/check-256-colors.gif | Bin 0 -> 1227 bytes extra/images/testing/monochrome.gif | Bin 0 -> 45 bytes .../images/testing/symbol-word-16-colors.gif | Bin 0 -> 142 bytes 4 files changed, 29 insertions(+) create mode 100644 extra/images/gif/gif-tests.factor create mode 100644 extra/images/testing/check-256-colors.gif create mode 100644 extra/images/testing/monochrome.gif create mode 100644 extra/images/testing/symbol-word-16-colors.gif diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor new file mode 100644 index 0000000000..1c4a80107e --- /dev/null +++ b/extra/images/gif/gif-tests.factor @@ -0,0 +1,29 @@ +! Copyright (C) 2009 Keith Lazuka. +! See http://factorcode.org/license.txt for BSD license. +USING: accessors images.gif io io.encodings.binary io.files +math namespaces sequences tools.test math.bitwise ; +IN: images.gif.tests + +: path>gif ( path -- loading-gif ) + binary [ input-stream get load-gif ] with-file-reader ; + +: gif-example1 ( -- loading-gif ) + "resource:extra/images/testing/symbol-word-16-colors.gif" path>gif ; + +: gif-example2 ( -- loading-gif ) + "resource:extra/images/testing/check-256-colors.gif" path>gif ; + +: gif-example3 ( -- loading-gif ) + "resource:extra/images/testing/monochrome.gif" path>gif ; + +: declared-num-colors ( gif -- n ) flags>> 3 bits 1 + 2^ ; +: actual-num-colors ( gif -- n ) global-color-table>> length 3 /i ; + +[ 16 ] [ gif-example1 actual-num-colors ] unit-test +[ 16 ] [ gif-example1 declared-num-colors ] unit-test + +[ 256 ] [ gif-example2 actual-num-colors ] unit-test +[ 256 ] [ gif-example2 declared-num-colors ] unit-test + +[ 2 ] [ gif-example3 actual-num-colors ] unit-test +[ 2 ] [ gif-example3 declared-num-colors ] unit-test diff --git a/extra/images/testing/check-256-colors.gif b/extra/images/testing/check-256-colors.gif new file mode 100644 index 0000000000000000000000000000000000000000..df83efaf55df781953fb2cce13186bb95c2ddfd4 GIT binary patch literal 1227 zcmZ?wbhEHblw*)(_|Cu}nIFQVI6Lx#qujjhjGJ1n<%pY9&q7!cbS9^V?} zxXsabi*IsQd`f3RR&Q$2+`O`>#TC;^>ZVr~EKPSl=j?sf+v$Rn_eJlJDae(9mo!rO%n$7?38>YKKD(uA!&bGOW1vTeb#-AmT(UA=kl`Yro5 z^c-q#xZlwFpta{w&$K7gWg*~vUT6_UAs?h+k0x)raSAl-rltP;jZf z^Y>3(xO(>To%7djUcPzv+L6bHj=ns6`r+}jPfne9f8xTUv)3P9x%u?k{fBoSJ-z?n z)x9Ur9zA{e_~o1DZ{EFn`{DJ6Pw&2b{rvsM_a8sL|NHmv%$YL`qk#4yp!k!8k%6Iu zK?h_NC{HkO{9_R0l=0ZGz_F>F=jO?v7Yh!zx3k>c+1PU7a5wA!E1Zj!I$e7tel9RH zPHCL#tf2o*($bZM$y6g)%4UlYuc(X^-gU-U8 zj!p&JCMYT&%@34pR9YGK^V8#&HU`0U3Su44J-hhi9WwrIaOhrW6|&^P3kC+J$zPcT zFR}2j@qB3HR2L~_P7nDrL5#_LgXwKKHxmU0c8<0HC+Reghfc938Eis|kro9DIW?X< zI4W*8r@T?Va-*RmgY<$E&U~^g7Z{`yRMptI*>W5Xaq??j4CwDT;;6trK{9|j&Vx&Y notrC#fq~7&%~C*GjNw3oP;2JQ8&W(C>S|8DC$0rMFjxZs@K%pL literal 0 HcmV?d00001 diff --git a/extra/images/testing/monochrome.gif b/extra/images/testing/monochrome.gif new file mode 100644 index 0000000000000000000000000000000000000000..de74e65eb1d6f01b022983cc4e24a5f3e0315a1c GIT binary patch literal 45 ycmZ?wbhEHbWLWCLu-c7bqX)xQPli=q3_E=p4h1rt4q>KUp{#82A}sU)iLIVwzuaN^it s&~we=j>V)1j-WpW?k#nHBgr#;zFwn&h*8UmFP}St*57!e%*0>~03)k0K>z>% literal 0 HcmV?d00001 From e9c780ba28b63306c468f201d71adb601e4403e6 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Thu, 24 Sep 2009 14:54:35 -0400 Subject: [PATCH 03/19] images.gif: Decompression now works. Still need to implement transparency and merge with TIFF LZW code --- extra/compression/lzw-gif/lzw-gif.factor | 116 ++++++++++++++++++ extra/images/gif/gif-tests.factor | 29 ++++- extra/images/gif/gif.factor | 37 ++++-- extra/images/testing/monochrome.gif | Bin 45 -> 51 bytes extra/images/testing/noise.bmp | Bin 0 -> 49208 bytes extra/images/testing/noise.gif | Bin 0 -> 21181 bytes .../images/testing/symbol-word-16-colors.gif | Bin 142 -> 0 bytes extra/images/testing/symbol-word.gif | Bin 0 -> 129 bytes 8 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 extra/compression/lzw-gif/lzw-gif.factor create mode 100644 extra/images/testing/noise.bmp create mode 100644 extra/images/testing/noise.gif delete mode 100644 extra/images/testing/symbol-word-16-colors.gif create mode 100644 extra/images/testing/symbol-word.gif diff --git a/extra/compression/lzw-gif/lzw-gif.factor b/extra/compression/lzw-gif/lzw-gif.factor new file mode 100644 index 0000000000..1d98fdd4ee --- /dev/null +++ b/extra/compression/lzw-gif/lzw-gif.factor @@ -0,0 +1,116 @@ +! Copyright (C) 2009 Doug Coleman, Keith Lazuka +! See http://factorcode.org/license.txt for BSD license. +USING: accessors combinators io kernel math namespaces +prettyprint sequences vectors ; +QUALIFIED-WITH: bitstreams bs +IN: compression.lzw-gif + +SYMBOL: clear-code +4 clear-code set-global + +SYMBOL: end-of-information +5 end-of-information set-global + +TUPLE: lzw input output table code old-code initial-code-size code-size ; + +SYMBOL: table-full + +: initial-uncompress-table ( -- seq ) + end-of-information get 1 + iota [ 1vector ] V{ } map-as ; + +: reset-lzw-uncompress ( lzw -- lzw ) + initial-uncompress-table >>table + dup initial-code-size>> >>code-size ; + +: ( code-size input -- obj ) + lzw new + swap >>input + swap >>initial-code-size + dup initial-code-size>> >>code-size + BV{ } clone >>output + reset-lzw-uncompress ; + +ERROR: not-in-table value ; + +: lookup-old-code ( lzw -- vector ) + [ old-code>> ] [ table>> ] bi nth ; + +: lookup-code ( lzw -- vector ) + [ code>> ] [ table>> ] bi nth ; + +: code-in-table? ( lzw -- ? ) + [ code>> ] [ table>> length ] bi < ; + +: code>old-code ( lzw -- lzw ) + dup code>> >>old-code ; + +: write-code ( lzw -- ) + [ lookup-code ] [ output>> ] bi push-all ; + +: maybe-increment-code-size ( lzw -- lzw ) + dup [ table>> length ] [ code-size>> 2^ ] bi = + [ [ 1 + ] change-code-size ] when ; + +: add-to-table ( seq lzw -- ) + [ table>> push ] + [ maybe-increment-code-size 2drop ] 2bi ; + +: lzw-read ( lzw -- lzw n ) + [ ] [ code-size>> ] [ input>> ] tri bs:read ; + +DEFER: lzw-uncompress-char +: handle-clear-code ( lzw -- ) + reset-lzw-uncompress + lzw-read dup end-of-information get = [ + 2drop + ] [ + >>code + [ write-code ] + [ code>old-code ] bi + lzw-uncompress-char + ] if ; + +: handle-uncompress-code ( lzw -- lzw ) + dup code-in-table? [ + [ write-code ] + [ + [ + [ lookup-old-code ] + [ lookup-code first ] bi suffix + ] [ add-to-table ] bi + ] [ code>old-code ] tri + ] [ + [ + [ lookup-old-code dup first suffix ] keep + [ output>> push-all ] [ add-to-table ] 2bi + ] [ code>old-code ] bi + ] if ; + +: lzw-uncompress-char ( lzw -- ) + lzw-read [ + >>code + dup code>> end-of-information get = [ + drop + ] [ + dup code>> clear-code get = [ + handle-clear-code + ] [ + handle-uncompress-code + lzw-uncompress-char + ] if + ] if + ] [ + drop + ] if* ; + +: register-special-codes ( first-code-size -- ) + [ + 1 - 2^ dup clear-code set + 1 + end-of-information set + ] keep ; + +: lzw-uncompress ( code-size seq -- byte-array ) + [ register-special-codes ] dip + bs: + + [ lzw-uncompress-char ] [ output>> ] bi ; diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor index 1c4a80107e..609f98c693 100644 --- a/extra/images/gif/gif-tests.factor +++ b/extra/images/gif/gif-tests.factor @@ -1,14 +1,16 @@ ! Copyright (C) 2009 Keith Lazuka. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors images.gif io io.encodings.binary io.files -math namespaces sequences tools.test math.bitwise ; +USING: accessors bitstreams compression.lzw-gif images.gif io +io.encodings.binary io.files kernel math math.bitwise +math.parser namespaces prettyprint sequences tools.test ; +QUALIFIED-WITH: bitstreams bs IN: images.gif.tests : path>gif ( path -- loading-gif ) binary [ input-stream get load-gif ] with-file-reader ; : gif-example1 ( -- loading-gif ) - "resource:extra/images/testing/symbol-word-16-colors.gif" path>gif ; + "resource:extra/images/testing/symbol-word.gif" path>gif ; : gif-example2 ( -- loading-gif ) "resource:extra/images/testing/check-256-colors.gif" path>gif ; @@ -16,6 +18,9 @@ IN: images.gif.tests : gif-example3 ( -- loading-gif ) "resource:extra/images/testing/monochrome.gif" path>gif ; +: gif-example4 ( -- loading-gif ) + "resource:extra/images/testing/noise.gif" path>gif ; + : declared-num-colors ( gif -- n ) flags>> 3 bits 1 + 2^ ; : actual-num-colors ( gif -- n ) global-color-table>> length 3 /i ; @@ -27,3 +32,21 @@ IN: images.gif.tests [ 2 ] [ gif-example3 actual-num-colors ] unit-test [ 2 ] [ gif-example3 declared-num-colors ] unit-test + +: >index-stream ( gif -- seq ) + [ image-descriptor>> first-code-size>> ] + [ compressed-bytes>> ] bi + lzw-uncompress ; + +[ + BV{ + 0 0 0 0 0 0 + 1 0 0 0 0 1 + 1 1 0 0 1 1 + 1 1 1 1 1 1 + 1 0 1 1 0 1 + 1 0 0 0 0 1 + } +] [ gif-example3 >index-stream ] unit-test + + diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index cbe7fa5f3a..4744f55a6b 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -1,11 +1,11 @@ ! Copyrigt (C) 2009 Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors arrays combinators constructors destructors -images images.loader io io.binary io.buffers -io.encodings.binary io.encodings.string io.encodings.utf8 -io.files io.files.info io.ports io.streams.limited kernel make -math math.bitwise math.functions multiline namespaces -prettyprint sequences ; +USING: accessors arrays assocs combinators compression.lzw-gif +constructors destructors grouping images images.loader io +io.binary io.buffers io.encodings.binary io.encodings.string +io.encodings.utf8 io.files io.files.info io.ports +io.streams.limited kernel make math math.bitwise math.functions +multiline namespaces prettyprint sequences ; IN: images.gif SINGLETON: gif-image @@ -42,7 +42,7 @@ packed delay-time color-index block-terminator ; TUPLE: image-descriptor -left top width height flags lzw-min-code-size ; +left top width height flags first-code-size ; TUPLE: plain-text-extension introducer label block-size text-grid-left text-grid-top text-grid-width @@ -97,7 +97,7 @@ M: input-port stream-peek1 2 read le> >>width 2 read le> >>height 1 read le> >>flags - 1 read le> >>lzw-min-code-size ; + 1 read le> 1 + >>first-code-size ; : read-graphic-control-extension ( -- graphic-control-extension ) \ graphics-control-extension new @@ -152,7 +152,7 @@ ERROR: unimplemented message ; : read-global-color-table ( loading-gif -- loading-gif ) dup color-table? [ - dup color-table-size read >>global-color-table + dup color-table-size read 3 group >>global-color-table ] when ; : maybe-read-local-color-table ( loading-gif -- loading-gif ) @@ -220,8 +220,25 @@ ERROR: unhandled-data byte ; } case ] with-input-stream ; +: decompress ( loading-gif -- indexes ) + [ image-descriptor>> first-code-size>> ] + [ compressed-bytes>> ] bi + lzw-uncompress ; + +: apply-palette ( indexes palette -- bitmap ) + [ nth 255 suffix ] curry V{ } map-as concat ; + +: dimensions ( loading-gif -- dim ) + [ image-descriptor>> width>> ] [ image-descriptor>> height>> ] bi 2array ; + : loading-gif>image ( loading-gif -- image ) - ; + [ ] dip + [ dimensions >>dim ] + [ drop RGBA >>component-order ubyte-components >>component-type ] + [ + [ decompress ] [ global-color-table>> ] bi + apply-palette >>bitmap + ] tri ; ERROR: loading-gif-error gif-image ; diff --git a/extra/images/testing/monochrome.gif b/extra/images/testing/monochrome.gif index de74e65eb1d6f01b022983cc4e24a5f3e0315a1c..b0875faa615f2aca084f2ebe35449caa7224af85 100644 GIT binary patch literal 51 zcmZ?wbhEHbWMg1sXkcLY|NlP&1B2pE79h#MpaUX6G7L;yE%NLKkMW0RtzclV1^^lk B3U~kj literal 45 ycmZ?wbhEHb1wd6z(}2hBE-@IB4`JXv{|HXUH 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 diff --git a/extra/images/testing/symbol-word.gif b/extra/images/testing/symbol-word.gif new file mode 100644 index 0000000000000000000000000000000000000000..101a48a880cb33a5c1f26cfd6761b9d347216bbf GIT binary patch literal 129 zcmZ?wbhEHbWLWCLu-c7bqX)xQPli=q3_E=p4h1rt4q>KUo+V82A} Date: Fri, 25 Sep 2009 09:33:48 -0400 Subject: [PATCH 04/19] images.gif: added transparent pixel support --- extra/images/gif/gif.factor | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index 4744f55a6b..6672ff456c 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -37,9 +37,7 @@ ERROR: unknown-extension n ; ERROR: gif-unexpected-eof ; TUPLE: graphics-control-extension -label block-size raw-data -packed delay-time color-index -block-terminator ; +flags delay-time transparent-color-index ; TUPLE: image-descriptor left top width height flags first-code-size ; @@ -67,6 +65,8 @@ CONSTANT: graphic-control-extension HEX: f9 CONSTANT: comment-extension HEX: fe CONSTANT: application-extension HEX: ff CONSTANT: trailer HEX: 3b +CONSTANT: graphic-control-extension-block-size HEX: 04 +CONSTANT: block-terminator HEX: 00 : ( -- loading-gif ) \ loading-gif new @@ -101,9 +101,11 @@ M: input-port stream-peek1 : read-graphic-control-extension ( -- graphic-control-extension ) \ graphics-control-extension new - 1 read le> [ >>block-size ] [ read ] bi - >>raw-data - 1 read le> >>block-terminator ; + 1 read le> graphic-control-extension-block-size assert= + 1 read le> >>flags + 2 read le> >>delay-time + 1 read le> >>transparent-color-index + 1 read le> block-terminator assert= ; : read-plain-text-extension ( -- plain-text-extension ) \ plain-text-extension new @@ -147,6 +149,8 @@ ERROR: unimplemented message ; : interlaced? ( image -- ? ) flags>> 6 bit? ; inline : sort? ( image -- ? ) flags>> 5 bit? ; inline : color-table-size ( image -- ? ) flags>> 3 bits 1 + 2^ 3 * ; inline +: transparency? ( image -- ? ) + graphic-control-extensions>> first flags>> 0 bit? ; inline : color-resolution ( image -- ? ) flags>> -4 shift 3 bits ; inline @@ -225,18 +229,26 @@ ERROR: unhandled-data byte ; [ compressed-bytes>> ] bi lzw-uncompress ; -: apply-palette ( indexes palette -- bitmap ) - [ nth 255 suffix ] curry V{ } map-as concat ; +: colorize ( index palette transparent-index/f -- seq ) + pick = [ 2drop B{ 0 0 0 0 } ] [ nth 255 suffix ] if ; + +: apply-palette ( indexes palette transparent-index/f -- bitmap ) + [ colorize ] 2curry V{ } map-as concat ; : dimensions ( loading-gif -- dim ) [ image-descriptor>> width>> ] [ image-descriptor>> height>> ] bi 2array ; +: ?transparent-color-index ( loading-gif -- index/f ) + dup transparency? + [ graphic-control-extensions>> first transparent-color-index>> ] + [ drop f ] if ; + : loading-gif>image ( loading-gif -- image ) [ ] dip [ dimensions >>dim ] [ drop RGBA >>component-order ubyte-components >>component-type ] [ - [ decompress ] [ global-color-table>> ] bi + [ decompress ] [ global-color-table>> ] [ ?transparent-color-index ] tri apply-palette >>bitmap ] tri ; From c0a8334d98ace4de8cd8697b20f1dd5d71f9d9f4 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Fri, 25 Sep 2009 09:34:29 -0400 Subject: [PATCH 05/19] images.gif: added more tests --- extra/images/gif/gif-tests.factor | 51 ++++++++++++++++-- extra/images/testing/alpha.gif | Bin 0 -> 44 bytes extra/images/testing/astronaut_animation.gif | Bin 0 -> 10316 bytes .../{check-256-colors.gif => checkmark.gif} | Bin .../testing/{symbol-word.gif => circle.gif} | Bin 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 extra/images/testing/alpha.gif create mode 100644 extra/images/testing/astronaut_animation.gif rename extra/images/testing/{check-256-colors.gif => checkmark.gif} (100%) rename extra/images/testing/{symbol-word.gif => circle.gif} (100%) diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor index 609f98c693..b62565ffbc 100644 --- a/extra/images/gif/gif-tests.factor +++ b/extra/images/gif/gif-tests.factor @@ -2,7 +2,7 @@ ! See http://factorcode.org/license.txt for BSD license. USING: accessors bitstreams compression.lzw-gif images.gif io io.encodings.binary io.files kernel math math.bitwise -math.parser namespaces prettyprint sequences tools.test ; +math.parser namespaces prettyprint sequences tools.test images.viewer ; QUALIFIED-WITH: bitstreams bs IN: images.gif.tests @@ -10,10 +10,10 @@ IN: images.gif.tests binary [ input-stream get load-gif ] with-file-reader ; : gif-example1 ( -- loading-gif ) - "resource:extra/images/testing/symbol-word.gif" path>gif ; + "resource:extra/images/testing/circle.gif" path>gif ; : gif-example2 ( -- loading-gif ) - "resource:extra/images/testing/check-256-colors.gif" path>gif ; + "resource:extra/images/testing/checkmark.gif" path>gif ; : gif-example3 ( -- loading-gif ) "resource:extra/images/testing/monochrome.gif" path>gif ; @@ -21,8 +21,21 @@ IN: images.gif.tests : gif-example4 ( -- loading-gif ) "resource:extra/images/testing/noise.gif" path>gif ; +: gif-example5 ( -- loading-gif ) + "resource:extra/images/testing/alpha.gif" path>gif ; + +: gif-example6 ( -- loading-gif ) + "resource:extra/images/testing/astronaut_animation.gif" path>gif ; + +: gif-all. ( -- ) + { + gif-example1 gif-example2 gif-example3 gif-example4 gif-example5 + gif-example6 + } + [ execute( -- gif ) loading-gif>image image. ] each ; + : declared-num-colors ( gif -- n ) flags>> 3 bits 1 + 2^ ; -: actual-num-colors ( gif -- n ) global-color-table>> length 3 /i ; +: actual-num-colors ( gif -- n ) global-color-table>> length ; [ 16 ] [ gif-example1 actual-num-colors ] unit-test [ 16 ] [ gif-example1 declared-num-colors ] unit-test @@ -49,4 +62,34 @@ IN: images.gif.tests } ] [ gif-example3 >index-stream ] unit-test +[ + B{ + 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 + 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 + 0 0 0 255 0 0 0 255 255 255 255 255 255 255 255 255 0 0 0 255 0 0 0 255 + 0 0 0 255 0 0 0 255 0 0 0 255 0 0 0 255 0 0 0 255 0 0 0 255 + 0 0 0 255 255 255 255 255 0 0 0 255 0 0 0 255 255 255 255 255 0 0 0 255 + 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 + } +] [ gif-example3 loading-gif>image bitmap>> ] unit-test +[ + BV{ + 0 1 + 1 0 + } +] [ gif-example5 >index-stream ] unit-test + +[ + B{ + 255 000 000 255 000 000 000 000 + 000 000 000 000 255 000 000 255 + } +] [ gif-example5 loading-gif>image bitmap>> ] unit-test + +[ 100 ] [ gif-example1 >index-stream length ] unit-test +[ 870 ] [ gif-example2 >index-stream length ] unit-test +[ 16384 ] [ gif-example4 >index-stream length ] unit-test + +! example6 is a GIF animation and the first frame contains 1768 pixels +[ 1768 ] [ gif-example6 >index-stream length ] unit-test diff --git a/extra/images/testing/alpha.gif b/extra/images/testing/alpha.gif new file mode 100644 index 0000000000000000000000000000000000000000..c4c38bdaf02a8625fbdbdde2796402d669449261 GIT binary patch literal 44 scmZ?wbhEHbWMW`qXkcXc&j137KUo+V7#JCJKpc<^0~50g6Dxx?0F_V$2mk;8 literal 0 HcmV?d00001 diff --git a/extra/images/testing/astronaut_animation.gif b/extra/images/testing/astronaut_animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..8c768480fea417aa54f0a60ca54711bf28c2f8f2 GIT binary patch literal 10316 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 mk+FyrQ9x68)bqM^Pg8>{e9 Date: Fri, 25 Sep 2009 11:12:40 -0400 Subject: [PATCH 06/19] gif: preparing for LZW re-integration with TIFF --- extra/compression/lzw-gif/lzw-gif.factor | 19 +++++++++++-------- extra/images/gif/gif-tests.factor | 6 +++--- extra/images/gif/gif.factor | 6 +++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/extra/compression/lzw-gif/lzw-gif.factor b/extra/compression/lzw-gif/lzw-gif.factor index 1d98fdd4ee..01e94d5114 100644 --- a/extra/compression/lzw-gif/lzw-gif.factor +++ b/extra/compression/lzw-gif/lzw-gif.factor @@ -13,8 +13,6 @@ SYMBOL: end-of-information TUPLE: lzw input output table code old-code initial-code-size code-size ; -SYMBOL: table-full - : initial-uncompress-table ( -- seq ) end-of-information get 1 + iota [ 1vector ] V{ } map-as ; @@ -22,11 +20,11 @@ SYMBOL: table-full initial-uncompress-table >>table dup initial-code-size>> >>code-size ; -: ( code-size input -- obj ) +: ( input code-size -- obj ) lzw new - swap >>input swap >>initial-code-size dup initial-code-size>> >>code-size + swap >>input BV{ } clone >>output reset-lzw-uncompress ; @@ -103,14 +101,19 @@ DEFER: lzw-uncompress-char drop ] if* ; -: register-special-codes ( first-code-size -- ) +: register-special-codes ( first-code-size -- first-code-size ) [ 1 - 2^ dup clear-code set 1 + end-of-information set ] keep ; -: lzw-uncompress ( code-size seq -- byte-array ) - [ register-special-codes ] dip - bs: +: lzw-uncompress ( bitstream code-size -- byte-array ) + register-special-codes [ lzw-uncompress-char ] [ output>> ] bi ; + +: lzw-uncompress-msb0 ( seq code-size -- byte-array ) + [ bs: ] dip lzw-uncompress ; + +: lzw-uncompress-lsb0 ( seq code-size -- byte-array ) + [ bs: ] dip lzw-uncompress ; diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor index b62565ffbc..629ab300d4 100644 --- a/extra/images/gif/gif-tests.factor +++ b/extra/images/gif/gif-tests.factor @@ -47,9 +47,9 @@ IN: images.gif.tests [ 2 ] [ gif-example3 declared-num-colors ] unit-test : >index-stream ( gif -- seq ) - [ image-descriptor>> first-code-size>> ] - [ compressed-bytes>> ] bi - lzw-uncompress ; + [ compressed-bytes>> ] + [ image-descriptor>> first-code-size>> ] bi + lzw-uncompress-lsb0 ; [ BV{ diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index 6672ff456c..8652e049e0 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -225,9 +225,9 @@ ERROR: unhandled-data byte ; ] with-input-stream ; : decompress ( loading-gif -- indexes ) - [ image-descriptor>> first-code-size>> ] - [ compressed-bytes>> ] bi - lzw-uncompress ; + [ compressed-bytes>> ] + [ image-descriptor>> first-code-size>> ] bi + lzw-uncompress-lsb0 ; : colorize ( index palette transparent-index/f -- seq ) pick = [ 2drop B{ 0 0 0 0 } ] [ nth 255 suffix ] if ; From 64c93d873fa71d2a443c00b3244ed09424cb523a Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Fri, 25 Sep 2009 15:12:44 -0400 Subject: [PATCH 07/19] lzw: integrating with gif and tiff --- basis/compression/lzw/lzw.factor | 77 ++++++++++++++--------- basis/images/tiff/tiff-tests.factor | 44 +++++++++++-- basis/images/tiff/tiff.factor | 5 +- extra/compression/lzw-gif/lzw-gif.factor | 9 ++- extra/images/testing/alpha.tiff | Bin 0 -> 23492 bytes extra/images/testing/bi.tiff | Bin 0 -> 8872 bytes extra/images/testing/color_spectrum.tiff | Bin 0 -> 31484 bytes extra/images/testing/cube.tiff | Bin 0 -> 936 bytes extra/images/testing/noise.tiff | Bin 0 -> 83548 bytes extra/images/testing/small.tiff | Bin 0 -> 27604 bytes extra/images/testing/square.tiff | Bin 0 -> 660 bytes 11 files changed, 97 insertions(+), 38 deletions(-) create mode 100644 extra/images/testing/alpha.tiff create mode 100644 extra/images/testing/bi.tiff create mode 100644 extra/images/testing/color_spectrum.tiff create mode 100644 extra/images/testing/cube.tiff create mode 100644 extra/images/testing/noise.tiff create mode 100644 extra/images/testing/small.tiff create mode 100644 extra/images/testing/square.tiff diff --git a/basis/compression/lzw/lzw.factor b/basis/compression/lzw/lzw.factor index 46a319662e..d186ad047c 100644 --- a/basis/compression/lzw/lzw.factor +++ b/basis/compression/lzw/lzw.factor @@ -1,39 +1,29 @@ ! Copyright (C) 2009 Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors alien.accessors assocs byte-arrays combinators -io.encodings.binary io.streams.byte-array kernel math sequences -vectors ; +USING: accessors combinators io kernel math namespaces +prettyprint sequences vectors ; +QUALIFIED-WITH: bitstreams bs IN: compression.lzw -QUALIFIED-WITH: bitstreams bs +SYMBOL: clear-code +4 clear-code set-global -CONSTANT: clear-code 256 -CONSTANT: end-of-information 257 +SYMBOL: end-of-information +5 end-of-information set-global -TUPLE: lzw input output table code old-code ; - -SYMBOL: table-full - -: lzw-bit-width ( n -- n' ) - { - { [ dup 510 <= ] [ drop 9 ] } - { [ dup 1022 <= ] [ drop 10 ] } - { [ dup 2046 <= ] [ drop 11 ] } - { [ dup 4094 <= ] [ drop 12 ] } - [ drop table-full ] - } cond ; - -: lzw-bit-width-uncompress ( lzw -- n ) - table>> length lzw-bit-width ; +TUPLE: lzw input output table code old-code initial-code-size code-size ; : initial-uncompress-table ( -- seq ) - 258 iota [ 1vector ] V{ } map-as ; + end-of-information get 1 + iota [ 1vector ] V{ } map-as ; : reset-lzw-uncompress ( lzw -- lzw ) - initial-uncompress-table >>table ; + initial-uncompress-table >>table + dup initial-code-size>> >>code-size ; -: ( input -- obj ) +: ( input code-size -- obj ) lzw new + swap >>initial-code-size + dup initial-code-size>> >>code-size swap >>input BV{ } clone >>output reset-lzw-uncompress ; @@ -55,15 +45,28 @@ ERROR: not-in-table value ; : write-code ( lzw -- ) [ lookup-code ] [ output>> ] bi push-all ; -: add-to-table ( seq lzw -- ) table>> push ; +: kdebug ( lzw -- lzw ) + dup "TIFF: incrementing code size " write + [ code-size>> pprint ] + [ " table length " write table>> length pprint ] bi + nl ; + +: maybe-increment-code-size ( lzw -- lzw ) + dup [ table>> length ] [ code-size>> 2^ 1 - ] bi = + [ kdebug [ 1 + ] change-code-size ] when ; + +: add-to-table ( seq lzw -- ) + [ table>> push ] + [ maybe-increment-code-size 2drop ] 2bi ; : lzw-read ( lzw -- lzw n ) - [ ] [ lzw-bit-width-uncompress ] [ input>> ] tri bs:read ; + [ ] [ code-size>> ] [ input>> ] tri bs:read ; DEFER: lzw-uncompress-char : handle-clear-code ( lzw -- ) + "CLEAR CODE" print reset-lzw-uncompress - lzw-read dup end-of-information = [ + lzw-read dup end-of-information get = [ 2drop ] [ >>code @@ -91,10 +94,10 @@ DEFER: lzw-uncompress-char : lzw-uncompress-char ( lzw -- ) lzw-read [ >>code - dup code>> end-of-information = [ + dup code>> end-of-information get = [ drop ] [ - dup code>> clear-code = [ + dup code>> clear-code get = [ handle-clear-code ] [ handle-uncompress-code @@ -105,7 +108,19 @@ DEFER: lzw-uncompress-char drop ] if* ; -: lzw-uncompress ( seq -- byte-array ) - bs: +: register-special-codes ( first-code-size -- first-code-size ) + [ + 1 - 2^ dup clear-code set + 1 + end-of-information set + ] keep ; + +: lzw-uncompress ( bitstream code-size -- byte-array ) + register-special-codes [ lzw-uncompress-char ] [ output>> ] bi ; + +: lzw-uncompress-msb0 ( seq code-size -- byte-array ) + [ bs: ] dip lzw-uncompress ; + +: lzw-uncompress-lsb0 ( seq code-size -- byte-array ) + [ bs: ] dip lzw-uncompress ; diff --git a/basis/images/tiff/tiff-tests.factor b/basis/images/tiff/tiff-tests.factor index 9905e7ad79..7a27a98251 100755 --- a/basis/images/tiff/tiff-tests.factor +++ b/basis/images/tiff/tiff-tests.factor @@ -1,10 +1,44 @@ ! Copyright (C) 2009 Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. -USING: tools.test images.tiff ; +USING: accessors images.tiff images.viewer io +io.encodings.binary io.files namespaces sequences tools.test ; IN: images.tiff.tests -: tiff-test-path ( -- path ) - "resource:extra/images/test-images/rgb.tiff" ; +: path>tiff ( path -- tiff ) + binary [ input-stream get load-tiff ] with-file-reader ; + +: tiff-example1 ( -- tiff ) + "resource:extra/images/testing/square.tiff" path>tiff ; + +: tiff-example2 ( -- tiff ) + "resource:extra/images/testing/cube.tiff" path>tiff ; + +: tiff-example3 ( -- tiff ) + "resource:extra/images/testing/bi.tiff" path>tiff ; + +: tiff-example4 ( -- tiff ) + "resource:extra/images/testing/noise.tiff" path>tiff ; + +: tiff-example5 ( -- tiff ) + "resource:extra/images/testing/alpha.tiff" path>tiff ; + +: tiff-example6 ( -- tiff ) + "resource:extra/images/testing/color_spectrum.tiff" path>tiff ; + +: tiff-example7 ( -- tiff ) + "resource:extra/images/testing/small.tiff" path>tiff ; + +: tiff-all. ( -- ) + { + tiff-example1 tiff-example2 tiff-example3 tiff-example4 tiff-example5 + tiff-example6 + } + [ execute( -- gif ) tiff>image image. ] each ; + +[ 1024 ] [ tiff-example1 ifds>> first bitmap>> length ] unit-test +[ 1024 ] [ tiff-example2 ifds>> first bitmap>> length ] unit-test +[ 131744 ] [ tiff-example3 ifds>> first bitmap>> length ] unit-test +[ 49152 ] [ tiff-example4 ifds>> first bitmap>> length ] unit-test +[ 16 ] [ tiff-example5 ifds>> first bitmap>> length ] unit-test +[ 117504 ] [ tiff-example6 ifds>> first bitmap>> length ] unit-test -: tiff-test-path2 ( -- path ) - "resource:extra/images/test-images/octagon.tiff" ; diff --git a/basis/images/tiff/tiff.factor b/basis/images/tiff/tiff.factor index c589349dff..da03f455b5 100755 --- a/basis/images/tiff/tiff.factor +++ b/basis/images/tiff/tiff.factor @@ -434,10 +434,13 @@ ERROR: bad-small-ifd-type n ; ERROR: unhandled-compression compression ; +: lzw-tiff-uncompress ( seq -- byte-array ) + 9 lzw-uncompress-msb0 ; + : (uncompress-strips) ( strips compression -- uncompressed-strips ) { { compression-none [ ] } - { compression-lzw [ [ lzw-uncompress ] map ] } + { compression-lzw [ [ lzw-tiff-uncompress ] map ] } [ unhandled-compression ] } case ; diff --git a/extra/compression/lzw-gif/lzw-gif.factor b/extra/compression/lzw-gif/lzw-gif.factor index 01e94d5114..8961abbf44 100644 --- a/extra/compression/lzw-gif/lzw-gif.factor +++ b/extra/compression/lzw-gif/lzw-gif.factor @@ -45,9 +45,15 @@ ERROR: not-in-table value ; : write-code ( lzw -- ) [ lookup-code ] [ output>> ] bi push-all ; +: kdebug ( lzw -- lzw ) + dup "GIF: incrementing code size " write + [ code-size>> pprint ] + [ " table length " write table>> length pprint ] bi + nl ; + : maybe-increment-code-size ( lzw -- lzw ) dup [ table>> length ] [ code-size>> 2^ ] bi = - [ [ 1 + ] change-code-size ] when ; + [ kdebug [ 1 + ] change-code-size ] when ; : add-to-table ( seq lzw -- ) [ table>> push ] @@ -58,6 +64,7 @@ ERROR: not-in-table value ; DEFER: lzw-uncompress-char : handle-clear-code ( lzw -- ) + "CLEAR CODE" print reset-lzw-uncompress lzw-read dup end-of-information get = [ 2drop diff --git a/extra/images/testing/alpha.tiff b/extra/images/testing/alpha.tiff new file mode 100644 index 0000000000000000000000000000000000000000..27215d6f0f411c1746d71c4daae30cff6453d031 GIT binary patch literal 23492 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 XZK9=>kdUAmzM%;>SW|OfY$*6YG-qSv literal 0 HcmV?d00001 diff --git a/extra/images/testing/color_spectrum.tiff b/extra/images/testing/color_spectrum.tiff new file mode 100644 index 0000000000000000000000000000000000000000..f596deb0f23491f310fe2d3bfacff380fbce2fda GIT binary patch literal 31484 zcmeEt1zc6zv+$v%L0X9;AtK!k(%s!0;1CjrIEM}a0Z}QDk`e(G6_An!i%`0|OO)>B z+Xv%%^ZoDl-uwT)@4dTW@4eQnS+izl&z?1B)?R9ApsOGd=p2X~bOJ&LN+`gIyr3a( zC}8B}YZ-J4D5IX0fi@I!6tr(}44{Vjt&9qk2|&OVL@EyBwq!Yu?6 zlk|3lSUW=DU@NGdgNr!xYIQv`*uh4e*??b-OU+dVYVV-v>kifNRoAulb+i_?VV0D@ z5%U)Dc6N1!!XaR9XD1gA5pQv32y$!@po|1_GK0@h;Ev+VhH9E%I1J|G=l}=va&WQp z2(f`}-676UFPOU{n43d@86fCxV=JO9EB}ogP!ngihr?Y(I61w%yg0mgIbiN~oZP~~ z!kk<@oIE`200z5-5p%v4loxma%hMZ3;`EsMj#w)LE+%`h&5MFK@c8Hab zl_0+@7mpyHH8T$Q+mKLihhKR8)u70h@56$@V#un949p4Ut^;TsD#6Di%qJ`$$Ro@z z#`)VefcjT_O$TqNlcB7GGt|Wc;8lW0Na!2(Z(Dxd#pstV+&sVP@>2_-3r?i)KPltC zK|!uRJ{~kuZzZQRNBd*_@e|ly=U?PeR9&nhukAxWK?_vKQ_Tdh; ze`09JmY7$_Dhs${2|&HP z?A%=J-2A%S0wTP?zYsf@pa@dgzrp<8Sq*06VC(a}Gaow_@GqqM-6{Xv`6tXDodN%9 z1A#;SrS3oD{@b2{n%ZAK2WRKAm;OwVYHA{~Flz+h;NVKK5??0~8<;iVhktp|AW#8* zUTZ-fb}N2vA*4qZWEX~V^Rin(ZEW~#Y;E{>`FPKC{cB?X9(ir7e+cV}aCbVJsBEk` zp-xbwOdf#YaetH6##+SYmw5;9dVnb*{jZc0{H*e|=H!5!Swx&5E_UL~-t0C|TL{7l z&Mfhpx&N!{c5?Vi=FE?Oqa()o`+>v&kS)yJ83LDZaE91H&%D4H;y36Y>+0(uz`P91 z(40T!{IARc-2b;8NcH@;AO8&Uw^aO(y8cnu-_pR}LjK3P{!!Q8(!k$B{>QrhQP-$MS!y8cnu-_pR}LjK3P{!!Q8)4(sQ22dAZslW?Z697>IYb>ZB zRBBXj5aDlf6(AvLB{g*9S`G+w@jt|4I>RHHS=WSq{6gICF(!J2x~ zs!B3odUj4uDOXn~C|CyO?23Rx-8ngBb!EYts!BS#z=8x9C#Re`J(&Kd1qcWd{cI@# zK=k0$bceY@-Qhl}09AIN1BVUVh8`I2>oC9K3xL`R51Tep0;9&nHVFx(WS<2lV;`0k>2(k(=26X(++Mm$)kA*`L0a$ke za@0Vs30XkW{Y#Flog9F?+7EUBHmGOACKq{t?&OI>-huGkaqH2dECF&<39$3{eNXZI@S;;z(@Q~GoIgw^~31^R`ZX~ z`Qcyw?@UG?mKTPMJ`Rc!5zWT1<-@KpyQ|7Ec5$}f${o{U+s`wt> z{LAjg17W56HR^SR08zMwgNrT9;%g-HuT5OP3QMJIt|L5vZ>o?$ZK(Axf#nY?{Oe%i zH-Y!xj7I+|D*JutjV<;)C`2mmd#H#k|A#*<{!~n4@1LjwwX-RaivtKvf0-Qr1F#@3 z-ydP~3kv)S{7Xy%jQYn+e~(Ci8|42@z5X@z--r2tHowLBUq8kW)&;(k(AXEKKpOkF7Wu20ACFxIy8}vTOIfW^t;3U$(#M5;eU4v|Dw0$aCPR; zgn7Vq&OY+|IoI)*Z}5NKw`{wLu7L6iIcCl>7LYV*Hg zg8wryf12b~|C!;DOC0|tGyJ#sPfAk>NU9(g{eB4?zlBs>TpT>WZ>)c9RSQ^q^8|kH zslu!se<5TGaq@t2A{TW2Dg=f5xc(Hi{_)Ziu&VdXq3fO{Dt=v_fFC8UsfWEF<DY6f+Ll1f2{NVg-OtkGYs3M5d))r`S0w=JkUNNoBy!s^gq*frkQg{+Q@yDNI6lj zeLcTvkrsf0&h|8Z)z1ZF6%7hpzRHZOg3fkyf`L0FNEhS|f`D8=9v}x06u84dG9Vbp z2{_$BN+4&T%?^Nb0B{G8E%0|XCUE`r@sp*XfPto4zi$5HPTvGTqPifQoPZm0`zR)` zMbO2@L&NI2HBkFTSlb%T4b;v)ZO{caTspyA?11{&p$fHy|6G@b!<@g>&)Cqmv;WqP z0tPx@dN?^)Lp}7JR6T$i%CE-3IBNsI=YbRJtfD0Q6KyOzcNoI;S0GH_TLNH1C)5Su z4D`X1(?L=PR#5>MP{)TL;4lTK3)CHOgUA;Qqzm}9Z^$@bk&zH&i;}Y)7`Xj+%Z~q6{)1tYMA0{x)ga3v2#T{TrJV2>A);JNSM zUm;`L!`ywPoE+@FYl`rz@W7`SU%^06tZ#d8fFiahu-x?r=*U$68Ti-exK?&&J3fC> zF7a3T(h5HsfG!|)mDEoB%`m9Td+B1_uo4cW_+b5!?S3{p{_$ zf*c5h2b?I9YiH%JZzAzo8ReVb0(L6{2hyNX&w3&8L74A>3sQ*>fm0PIPJ%$uHvsrk z;7vt29Xy=`5h=?n$phQ5kzQ31@G#3Dyt9pU7BXre)YBCZ!C4Jt1M~uVjDT)}@Xnn( zhkXtY8yk-p7YCP^j1Uixkc^Uqgp7oQk{Ivo_;&kR`Rx@29~T#&0H26}fQXcUfPfTv zAt3$Qh4^0~aQYfVd=3!jAsPxj2$dKGjTq&03=kdX^gW6sz$FIC*#R(&JkS8)&@nKv z&SB%=0+YtC8&N>0Xx|%&Kq%-aXsGC@7+9F+FwpRLfkt99^h@WtF{HF0B=l~;Ie zECF#Xo;55>IZJd*bmL)V3$zrOhrICt=`C)9fr{$yH`J+{*&XJm+$Gv)>)PjDaQoj6 zXTFz6C6wI3M;%il%$@bd8BdatmD{?wh}tu&4b?PM3x_F(9hW7{xKTPotgJ+V>EW=> z0hH>6tg{lWZuonK_x)S9&EpJGmE{wtU-k&iMcN-gFKJySd#CKXm~`WLb5~AF0zHp- zu%xv{m%I6$#H694dxw(u*cDDYr=LE8*mvC#zwdS>xyxzVs@1;8W8rz;=vCAyM!nxK9(}BS)dSkLS1CjCLBF0{lq0GlP&v_@P5n+#V$*xnA`dV2 zaq|O+-Dopw5Jz%C%>8Co3oOC|xoH+Y|gGP+%XlH>)f|#-$Q|R=|k_X9j{d5m}qV+5NyYq`$k7MHr zOPF?`Pj-SB>pEuBHdar&KD>FD+y$Gm#wRlsGmIjiry<5ona^T?*|pCGSK%*F%YS-d z5k98%a)>C(gPgBuc4lm@a^oD|l*MiGfW3Em^@!_Hz0VDcI;xwGq|Xbgn{bEla)*=- z6Q?SFlAt?(b<}9)!`QP?&61L$8ws5PxiUPF=@cd(6hZ8U+yJea68p$5OIuy3nW|-8 zZdPEI1L?C*azqTCx`&zY-2U|c0s(@x9k zF`}I6pxM(O0{@}wCQ zMxJaty7_N2T2u)6&CFCOpQYif)g(N7d6byVRTDQ9F`FrI{h*qpvW;G^8;teKD%Ve6W~qY6gvf-_H5}-H`(ZS(In)b?~=I`ljo#3_nDYKHm!(Q z1PZB72{-6N$-mb9;#1} zf+v{sfS@6xnFA2WK(@9{Suw2^jZIdB;tIrv<*p=C?(&Ap$ZVu=Jt9NQjC%dfZ01s( z@=2t93Pb!;C>E!k6%8i5@;)A6Z?&i^BN|I5N&xq!UF_!_B~ORW z=Cey@&Rhq$0y7>6LIbNZbH)70&BDra{BBp+(p>xwXuM=9iJJLpzH1=2O1>R#6q$<@y{c%kp8S2g<- zTL9F+#w|*%+(Hu6z%Fn{&-0?)Ez?W8R}kHFj*;f{f=|OX#?Y%}xVUPlS#b&Kt=M0E zh~Gm;EeK&0G8MyRx!DRSc=(-d(dQla(iE&oLLbtIeYMC`2c2dJCHi3EpX$ zO7iqeui$(>GO&BdMMo$ZU;F7$N^ByiJ@cT83Fa}s4|XNRIk>jQz>Y4toO9v0UgO;t z?E2Uo`rCBZxbO->aCtvfT+>3ABz_GuB_XNfLffd5CbnTBlkS2W;56Rak|AvZq08>e zud{j3yzhitU#Rb9gIs7RW{kO{NU;#VOY4GeEAGgN zt7Au(Q3J+oB@-{Xd}qU*wmzpz3J)nivEG=$a5Wjg&-Nb~7;Eyk*u!Zr8{_iC_Usr4 zB$2YLkf6HBmW**6pu9`KVq#@jzXt9S7|NE7z1VBq(=v+Qx>3)URId3>NL9k`c3f&*O_x7z`AlQL z-Q_U?Q)u70uwXsBz)`5t?<_XJJ?=H8!Tx%eCJ8p}8K@AG3RZAs#AeM>pd0YbzDsj<8nIBqbrdt5(pwAW0z zgc|vx0JZ6HT5ISoLX+u8_$lSFi7cVyyZw_Yp)043#j1G`yrbA$!~>Z z&XEc`hQ5RhN>>QH%Yu{HbT)8^<`iN-)S-?bx<@M0Oq~{|%NMqeNhLjadz79SljfQs zE?K9gG-Q-17^Z*a-QeR@`e(Djj5v(%gxGfJm#*F=4w!r6PD2ibdkXg z^^PSf*Ly`_%9$GI-I#D*qNg(#t#yOlyH14UqzP#>;kV+5Z;LfG?^2xue?r4)EkKhQ zK!=cO$GTrvRjNQ-w%IZU$98jk)Q7P=Y<&`W{+Vtyr8Dy!ok z49bI$t8-hPNyR6Y6$)WwkoV2;@f^l2EY7?P{t-#>>YQpu5&Voc197}4(>G9BF>iF4 zVVyU(Q((yw&dupcDMm%K=Xr=Q`*0wrA4##Lk$@$8%rvbEwvo9Qz|Zj)a~Ib~-F! z8js7Q?iYRz*1F2~<=HmDqn^)k2F!BG&k)g1`>*C2ea=;X(OaC+S5;zowZXiA|82Pe zORtn&u0tWeKyCl+K8r8g0r?K++)yUP45YEY6qK1aw6I>3VGEEc<|As-S8lL;e(YC5 z?($~n*|y&GW20h^;;z9K8Poi$xW%t9MU6f-IX+8xnW_J=KJ%iESw7j5(!R#A0mU9? zw(Z0c0nf*#xRG+#KojWrlm+M}WG)c7nDUZGi^D23h?OV|5S%C|k&05%g+0GUXJfLt zrNwC#f40Up9ARci0xxJ-t`pSWA9E~W=NO}_F4PO#;>f zS>Jk#vrN>;M7YYmYZ4k?AEge6amPdnvopA0O>K9lSf1;HPz#fVlY`gGSHewH8sM4M zumo33$&dR^?`K$wG`w1>> zcrRxBmH8KL*}N{XeIhzKHZ*$A-FgGSzCju`=?;&b<)J;XDq4>E+}!-(Py4AH4Hjvv zAC+b_fn{t+P_3#+J^HKrM^r2v1M;HyS1RfZR;@n2L<#bwZ5$GffH{wS$lZAyGF)xp zu<{b+6{4?yo7Z8t}&SpD23)GCEJX6Fpi;BFaas6JSnGp0>3?i>b> z_JRZl#TGK$qQzUwgDxx_%sg;O->=)(5MPkR&DjW~=a@DNyKp{v#Rdq7a@&M zL|{2s_*LU#Ri^Lec++6DARGA5It$@4(&>w3)|2kv>`7BQv>2ndO1=8TE2AgcYwOcW z25$MDgy4W^=7ru`hEk8{LyW@-#?`$^5I&a?%I!(B8S17&w^NdSzNz+48GhWmuuHSA z){fRIpEs}7jC}EHVw$V-=-4iFoaWzsw3RW^ok(Z&nLpy$$|>5k_sah2VodM~?Pk{j z0q)8sR@MFlui6~Ux~Pt#X{nxY;Gn*oD*P4)MgyEH)@3fB_Q|JRnv(6nambXU*V=Ki zWVKgey+kGF{>ky-Oi;|uQp~|=2+`5%{4s0}v`TQob^dsb0)>bQRPcJY;~2!L0pi?7 zjZQvY<3%If2eOFZm)Izsviq(I=v)`kW3^D$ln-9=ZFh>Jy0T(&K`>oKF=L@kZp;*J!5ALl40g;e_RAOBokch=9r0r`@z$L2Sr5^PLNPRT@LZP(I_ptl8wl2- z&J|MNcg_(I?&G_55orz)tqtH$qf-CQeA~Ot{8Cx{e^z93m}}p>Q*x)bq~~{LYg)EJ*_DUtp(!Peon0@JnjKHHwZ~w4|UN#*|Hp7R|&qB9BEt` z`1v7iks9r~37ys)Eoe zZq#1w&g0-IVZOJ(%2Ulzyh1k>jfLIFdgHScPYOE;7Mok6a1Smwj~$)%6IShI?k+W2 z?KvJ27S{9is732s@%&ddqS#%_xk)IvdgN$yE^=?OGjDRDc`&h&=y2+=a>N^VMal5- z;_~*`pO8rL#tRA*TeEL!@p#aoZW{1yvhejlNZ*`LK7?aQR}u1Juoqjilz547r-6sFhekbM;ET*$6 zMzSfQ*|qypQs;ZF^~%ZeNy_FcNyBNRdky$?cI8U-dKU=$J64{ryvpowbEfa_;VF{47Dv9dAPHhZk9-In+7~nLnl}5K198D zQEF>WvvfrBeh_78mnLbKchMXh9-$6jk(N#!-BjQ?*7K~v_^R|6 zTD?6Qe6qrQn)Uo9y4!ke+vwYU;*z@7`o(p7dT#1vSO&!<8e}>O{1!@O2Kr=S`g{#w zGTZtxFa3l7#Xf{oU#d|;CK`f@oj<~a|Bhioj7eF%tos{dgpdAqfKge4THmwnHzyPi z!_Qe&5zCTj=*@}rEtrr+iI=9Em#v#ekLe>EEw&i6H_eRsoh1`dEIm<`LYgeJtsx1^ zkR2MmzI|~4DvN#+%YIJfer_{zTuXgH>m3Z^9R@s41w)=Fy+mf49UaIk{a7Ma%N;4} zvUCW!uz>)tt-iBu@Ck+G?aH?m#C<|2%E$(L4i1XeLeeEYWDr9R{eH%0=d@u zdDr=yj1wKNzbd-!S#rJKPMmDhL2uQeKgq!}-r-f3ePWAK;*iOm^6TW~j{1cz`qeH1 z)s~5sF8#}ziF?<`DV+4XoCH$r6FZ#sM_l`RoV?J;`z>4&5ADbo9D5946g1WY3^2V$ zx+(W_lhLeMO!k5lZUSR&1~_0n2Iq1WxR;C@g^ra9tJ}`*^>T>2ATvjTpl1@)y?<4? z{|$nI5T4|}LZN~13U)}+L6kudy9m#6CLb>kRzaATSE{W+iX*w4w;(YhsR%xR;=Nn# z=cQ+#!0Nv1=Ih1nYhdk5kr^OR>08c2K6$SJwc>o6oj*k_!eA9jArrV;W+=Gt!*D5J zAoqqLw(=mZbuytNC5e9_6h-hfh~iFA`F4oGvbPW|>u!R7UsJGGSEyH}d$MRS#XxYR z7fLdx!=Q#q(q>5cZdg*&O~I7VWWsQ7r5oM~H!mHLKTO27sviiOz0zBt0L zXraZMMi*nBR*+jh=%BA484S9cTpusg38FlVH;lit&wa}~CRAwRe#KnekXn=vRf14a zLgcm}6tbl^3xLlX=T8R9BABRZ%YFiRL~XNiR+rt2DuXVq#QnQc{MN ze%B#6+)#qS7R_w)W7Xw%VvdFY1-(^E|)JqSUTehkcn@M`Yjv zoariZ6O@Wk;=0s#m45LRX*Bs)KJi?9{?<40ZK_@U$fc@LR#U&CI#a55hf~$5^>3+_ z-qe7LYuM{e1>c!+K0kcY;IEf?I8@@V@}8RFy=Zx3jmP^@wXh?d`ZS|P;oVv>-e%Jq zhnLiGIi9ixJ4YNEq|{_RyeU|5ME0@9t!~uoy)mMJ+M>Cp+A>wCa`as^WV`u@yOlb) zI4$$zC@g*SNelHdlU{V^jZ@4*!l$f8Nrg<@TEFZ2yTC z)5VYJH0|PaU1_kU(fZD`(+)Gv9?_P#F|_6Y)*k9Ax;M(WmakZ|5Ff^v+5<=$5AS>& zGidi8=?6USC_~M$RR2*^|Dn`?nczSGr{+=Qz_Fy}QD2`p;oz}#W4cMVcwB!#O5btn z;8t4)O+aV5$8fFNU_e>_SkVYgVNU@5P{5^O(!)_Rs?l_Zq2r!zz`oP{hR4bWO*aQ= z*vF2?My`A!wXE8duAmsx>Z)xUHmMw~?U<}XnH-Cs6gL@8UmldWI8L)ZGJa`1gJJ4; zeXx#jMuMdFglvi?eI}59X18)GeRXPFWtx_0x=y2iTy3uI(x`;^q&e?ohRnRV)clFu z{K&*uhR#AA`@*=vCt8-dalPi?BT{f3?sOHU6_$O5=z>{-;TXlddFJA=(d78EkuT-b z89JXbA_oF%JJUsb0^iMPE*MIb%m?Ovp&gp9bC{(qrmHJoIQcLgsI^kNK0Dqtm$AGw z-nCpewH(;D7U=d_5_@Psy}qt;dc5V6`QmCiY&D2tF0gJfSPYNzBWrNXVjb3IjoXm- z;m5#1fTFkau*}9Ts*=bT(iMru9tNP7;H|gu>c{+8#Z| zmId)PUGTPK*xqS^M!jJ*ENuJKYiGjeASm~sZvDW*VCBRy`nmBp2P8Kd@vN%AcrVfMu+UInzmB!nL?r;- ze0Rxp&|lyJX4v~_u3N3r!j#dn+3x3#T*S$uBrOY@FMh^6Ong0e4IBQ^f)Au>>ydJ^ zA0e2bvC!kNG;MJ0k>(S4WEssNT8P{%cOJE;Ao(-hSuG_dc^cDu{htx`s*gh1a6L^m{Kk?wdWOaA}=(q8|3V}-i}%u_CmLC;PW18gT~__`++;P!*g*T zO^Ubta*NlUKa9uA>#&O|<@PviUr5(4G|KNTSsZB9^B~cTwtP8aDt_u!GX8d#Y~->d zQ(DWgdyRiNy>n{Ky3-b3fW(tKW3Zf&gH=aF|L8K<|04Bc$M$)<1KMZoXkt@|~aQ zsG8&M#$U{~XQd-+D!v&quds1fKJeZ<#U30L_t~nx2WvI4lUIvP-#4nhz>^MX>VDoL z@KO0{0;nB?ZbH%8Ef&M17~ktC@rbyEQ~1NW>Y_ArNjesKv&U%n%Qlj2@JKz6W0suy z{aQkmE2J$C1Mh_z4shxs7%QAx`QST~9h-*;{npse`39XlX)~lcCHK8lD*GCU7Wv|< zm+|_p&sivRBwl>Se{B-O&qPW9fonXh9~NXeq%U`QY_Lx)-f)Cyj@8y!;P!$UC)r$k zE?rLSut0Bl*mVEJZMipwdOb8{{wAL6hk+Ib#R|r1{kf@d%Pn$ZhtWayfSZqd$~FV0 zz448LtP&--t+@7Rap6;YX2M}MhC2$T)5M-kesdlYD0k-)S1eMjo%TW; z5q1kyD>#Ao(7Y#n><^^V+**0tA-5+5$jzR9_6^T;cBmj7$kePjp~`Ud-FcHa^miygNheKCH8A(569otgWYMDD_8Uq(<`0R1hS*n z3r{p6V16g{Z#E2>(TX;G-W(_pFhs|9nKMhIB)IuK!bF=(c|6mUqQz+`B^)WS6{sBxup(X=dj*#au@iFut2XcjcjPT@!oETCHU2 zC(jhGhIjiCw}n&v@(L@b{X_-J>H@}2X9MFxlNY*95mfz8h!>LgOyH>3*N7MD^fso= z?tnBon@g_kBGAnrxCN@k?MUpRt7^Q*y|LUkQ?ZASHOYu`<0{I?wNEVQ8Koq4MEay} zZgKcUkJ8*!eRclLCt?Cfj=&Aq#pbsa?6?qJ?hwtYOETwT{cdU^2nsitnTp0wr>L=S z$8Mm$OO_(1&%^i8+@U7&zOX~!v2|CAthLd$5_cNU(QfTJRhfMW0a(`}n%G68!Ml>? zQK{w4_4}#v3KKXCDsQdVq^~u4;wV2v_lq(#3KuT8G5 z@G10C&JqP(2v{`2JM*gMtx%9tq>7NH;#Fnx4eS-jG(R7y%oxnxu^bsd(YYE0?>x;@m&lu}7xT z%)gjV*O5mQuoQ7!?p;sqj#h1T5ENhN-M)-UDHf5UcTLT5H=)SZqXm0}ae8YddZj&7 z!3aV3m{ll^*CkNnD%;MLe3I1GKtV?P=Teh;1UY<{Bib8Hl$a1i54Mz}G;WKkJZ)Lc zhJTD+)RJtg2W4f^S0KD}Pt@DvQ`++4Vdv+9x{LWeT6SZPH2PtMo5~@h<9i6eK*j z(?hjsc{RYPaILPqf2fxJdAfN@!f>PEfq}v0010;i)KYRsLR~g8r+jkTvUoRiI+gmJdu*F%7N{Y|mE>p5l zw&V0B*SePwkuL_0G#X=YBLTBH zJmIL~sL7_6rBV^E_am3VrzqE0ag|tu8H>k_v$z)oXZG_2zR2LkQqqZAG#_WWb%(W= zg$ga)U9`DA?wPaSBG#ciCE+oCXi;+ssOXkqsuL z?+=NxK8f2tdm;JSl_2y1J-qEIVRqSz;@s=p!u{&_1#wy6t`OMO8Fs*c2a*UTv> zr|ED;#5}y+D(@6j+?1F$O{7Vq#dxnyusDl%8os_S%mW zO|5|{0kkg<&5q`*Ugpcy1U4AY`+tzIJ8JWcH;Ngmo`xv5KVufTX zb@jHF+wqr|2I?bXgmNylI=2>JS7$Fa#jKmt?acktFN7uH?vp z#2u+)$%RcxLxs>ALgE{`5qrcaM9YkRd-cIE;=34>mC%IB>vhkUuEl{9-bO}Sp43;* z)-9d8rb-|gJ6z8&L@DJ?p^HmmgM-~gaLsS`+7_l1+M$$pSZ(Mj6(h<0_toQVKjgWTPidV`=$zHszi~0J9>8&Xf={*VQySQ~Jf^T%W z2-vW$4e?4>4@h@iCV9bG|H1v8DOugi4D@6HG#l_E*PvicGHIcs=q;Xj7MJseL7b+{ zQmTToRAkZ*UdVJV%gpkUyywFcyY_ZgTK2x+dGsq%Hmn3k67NR|?%&&v*phy`r7S(G zB6A}J{V6}1u1Mx&P_QcnVJ}tmBF}ppHQCRCaixlH;o7fr%%W|~qfN2z*uRw78z&hx z#vg1*Wq`7aqq5OBZ`Rpt*ZN%uHq1> zgkIy5Gdhmgqe6+pmml?!yJ;kA_>SaALN3Sl?gupT6tLWAP|VF>DxvJCB8En>n{qFF zB1!!fHRGB_Eff~-DW2PI&LVC`kBsyWS2R3mhC3;|@NIUoA#8;;zlc<1jBmahsKl2@ z>6(V#%!|h7NkbVF{PLn)#i>G0bmLY0nAr@a_Zc7YGH)NmDncL!|qd zP;>jTH2#OyOvR%tg;t*@R~kOPPd|6_(MPeT%HBC2b&FKci|;sB8EhLFss?fY+GwhJG@J6P(750TU9D2 zVvi1GYf80rqJ4H%72ex^)Te=7C`&ZkesfM;aa%)nNBwTChSO|2+j>WuNju}Z#tSu# z(hWT7!4Cgjm80#Bqs>mztxmkxN?XSox#%754?E4!)t8QMQO|U;qjYQ~bn@eA=Hqqd z5?~E#go3!#i*m5`E``uwY8pm#$zf=jVJZ$Tcj=Mej=ig~w5OFP)K!M7)qtkenbW36 zsdco{jlPQKOxYCxzNL3T13|4IPSdT&tkuT+1U#Zmx}lv*r@lp5F*ZiQ*)@3&f1f}(o-uc7bc?99H-8x*u&1-d#+WL z?3yl(6kfV?FT1QR8I?|hL{EcwZ-9iZvr=z9X)i(<8$+^JD5qCX{cf2C4!&mhwv-+X zcTbd(o|>|5lxm-|t{y^9FThB*;`s-59UTmHU4AoNGqc`fGrd}#FiQ52F#(F$pb$f* zo+$CYTqS)ok$(6GeLY3}v?JXyi#~Bv6@D$fZF5~UC;e@w=LtrAX4mzd?fP>nfzMNQ z+7LL)j{3{6K6Xw0IST^}SA#Iu{sd10@qq5JAOmL)gXJ3?WhMp*{{0m@dQ71M4Soap zu&^s^A#GMkw}L{vnFsoe^~3HC%tZ|xiwx2r2KXHf^}-Di=KW*$jo9xSl*JqAg&CMd z4`BotB_t1psTwxK4luWL+8cwgo3#%B( z&mT#z9x0<8LA)ASE*;rV9NDfj>fRn{duQ^#XjHD&)a>0T^v!6%8`HZzreUuQm!FP0 zw;J(zjJ7qIF1KS%>V`bMI)beoaa`DQtIjmxBf(hjXg=J)tbdfI!qmQL^f=U{ZFp2~ z#7J&5f3DXI(btzdI7;?FH+O2}cxWVje01)i+1!|UK$f}L#5n%sxqz7w#PoQ2pJ{D_ zc|yaubC~(|*y!9`m|3yeC2g!@NQzq1cy06qV$fJKd?IYw6k}#2Y~6_eXdH19JL-72 z?ew-fXkzlSDcONJ*`%e|g^5?_z0ODG;V2W^BN^=NlhHU9OgokWm=@0amh8EfdI!;C zD>C{x<15%xm+?%^adFNChS^^UiP|Eg3<}xfoNSx1hzhWHOap09f{as7y}sFBc05gU zY)n9J9!)JGvwE3l7WH&`p2wOxdnVz;I(Kb`fITeW4kVHjYmYmW_M(|B-%Oy4wWrkh zE9p#qxmkT#^NNu7QL8o?!jt4Ycp1vpbt*RfoKv>Sv+G>bby_wO3Q$^U8(;j{aQVp{ zok@8mn-grCdBd6JR-1NXXr0Y{B+U};OUKW8pGXRAFcc5m*LIkaCB z`dAej&TboMZfkoTVrdv+e|ct|BVr%RhGuYX$7TUjZh?TsmIJatu5YJxe=g8*9x*=u z$~&^a$JWJRA;W8-PHn*)Vn^#?OB-bO>9d{ujVXelIRV8&bAM=~`a*#wZokGS^Xs27 z)a^1t=j8+K)ZIUk?bu(w`$<09?gYBZHBwo@Ef`^V{j0p|b!m$gpRX50E$)c}*XIjAJ#J2sf8_Y8;C$K*DVNhoK*y5}AE(PN*8R^TkBQ6bs^t_y&7MWFLmD8TeHK!ov(nMtix93UylVFfxWH+IBrQOtNN;IO&8tTH{9NEt$UHXosuoL@2_Tp-C&e3 ztP880xNe!K*37i-Fq-xL)Ae1_4NiLdrt|AT3~mN&YvGd{8dqGC@He7P+yh%YC@y_5tIojx8-Ar6uG7#Q)!tOCZvL3_hfyEEUJYLk0+~T~pC2+jiA?Lmz z0XGuef{A(*D%xvE!4UXn0CpmKR%s2iHQ%GSX^kD2uvCcEGP=+q6wa8#J3E(>8cP2D>>zS zc`5|*Ti6WJm_97uJ`LIpioAbZZhO42bQp6KRDSG!DtitM*t8Y+dQwx7niK^qOgbyp zUR9preAu@FNU0UJ2N#KH)D3vnIv`*O^9?|Faj;KA`7FbLJg$%pNg z`n#ldWo8P$fafyaWuLZ4n&uN1pLCln4qczOgGPOv@f3AxoDnWhwObsnPD0bmpEB7N zaXuw6QE8hm7yXzqkd;2Pb>RL*kX7nKzn5QZfh0x4@Z?(~8MiuyzArm|uB%(cCW!b2 z1b5MKVZY?T(5b}I9sj9HB9g|H*M)fKt53PEUfqArx#@Bw{5cIg`Zg~#9aP$BL%wk> zx|bN`y`q@Vm$$HDJ8U_k?B}?0FW<2(z3$xbBam@J7g?2ASx>$YUI-5cH>b>wtiQEjft zr)hPcUEpYsM^h1L<*m7Xt8D{*%Kza$UM|1deZCPY4W0LgqFTLX7Zv%mYwQ$SFcAfJQj{osOqKgY zi&U>o4We1tmMuNBTlR&*xR)&Q;wZ&-gUwLl_C=ymy=c^W^X=tbp9x-1I-Lnh7b+Hu z3VE!c8A?oSVAYZABxK4v{7SNU3I|hwxnTI!yb-N<l|5OuG4voe1gjG6Vq+P9$V z?y|_MY~g7?{M|xL6U?SZD^E`>N^CDY-d9|$mXO4F5NvPqaBV>AM32nmu}Y=O#So81 zw;jxuvW>2f9km`N<_`6RqnQ`0w+tp2I^kV*tQ{Wqbi6IwJ{=axTZb_UL!Gspj-xNC zne_%t&0v0$%V|R|$in<3igklTJ8VFB3_*6t(Ne4-kQCH%99! zFz_B8(T2Ld34B|D*MF%eIj}R2re0rvJF%53d{LU}ongfJTNiyIUS`m}YYryOvXRoC zu_pn-Zt@PpQ}VaNjO5Ju_zZZ^pso~Vd7+*QGqS=FUD84<3M)s}Ik0Fgb( z84~Lw^?R=Obeb!?XrVSS3B~fd3OVG0C|8*7+#B=6%|b9wvt`EfxM(RV1#msa3s1gx zPeVRilEx;dFy&sOz4D?SUlw22gA#QTq9^rSxl0YnC8#zk`zqY?)QxE+EH!NonF7qZ zz3IjBh&FFET-&tt)RJ&_b?i351?suX_{#zeEHl?;QzstAzYXcUH~vL_jRD<#`WEG- z6V9-$2bp~49$k^A@(b_heYOIll$DbBukbVG^awa<8*7Ozimj%9;*0La;Z^-KN1MYp z{6K3MD#=2_nyXuNzk4`T;~5#-(|cNRI?rJ0pY?>FkoG^;JxFD?CzE@!P4H0ndWqiW zKEtQhTdt@?GO~`B0_kS(ARi7M%{rRC%`a2N*FTt(XTcCpt~wxrS~LeOdZ+X zR-i4Zp3{@SQ1%63byZ2~6(fGx_620k)qNM{6szCauhPU;dS$F@nB;k`v{hE^7JM-u zDYE0pU#TtAUo$&wadSCtuP=W*KZ$!J-2|paOMF`#v!2MaJoc*2;2q}F#YNakKS2e# z`^vBw){PalgZiWed`sN6TRDN`Z!1(tEKE5g*UhiS3C0snjY{1uN~C@_*e*Xk%5s}~ zUbM+jhHk>IGLpNY^}Y8y25V|q6n70H^=o^4%iGP8jouw^_Ho&)Miy^wU=lSA`Eks$ z@!IfSR%uQiVYI#5a?f3^v3B2%%MPz7X)^=T;$6IFD;62gtM1pje<(3OdTOyT@7}&w zE%M1}B5TXmyPc{~(Iy`wl`r~!r>_D3fLkXfs8Rb7!;wRjIN8(9$5Fk$sk(Mf)fuH3 z$(cCQE~)^x2_>oWj87T!)8Jn*$*UA3ZyQfd z^@HIL(eDN>kBROERJW}s3f67(`cwEWPoLJ8_t{R0s@!nxIuQjuBpui3`^R4HJ^z&U?*Y$z7lH0x6GBXm z!{0mCbn%_UoEh^PYR#>t7Ae{KvTW-s|4{4|DH1|FiYJ$JczHk@EgGui+m*+I@o! z_@7hj{a>~Ae&^Wx+&kv{ufy{_SK|D?liL2D$NK+2>@Xis%KiQ$yZ@iN`rpg@eZCev zfN&16JD`0yD0&=UKurv8ub0#GReZ?yifF#j+v_^;0a z&y@r44+Kx00Z>B(FUJDUe*=(J1u!)KP%8!SR{Rh>1h7E?5N!dlYXooG29RX?Fl7TU ze*_%M9>kT@QI7y*y|f?@t0Vb=B#O$QKB2N0zPa2EBDtqPEZ1<-*Akhu4dy#>(0 z2=L1I(6tA!sRU5b3lOd_5Y-EC%L}l-4p7K2FzpOb@ad4x3UJd8(Ay1>0}C+w5D>=> zQ1cG46Atke5fEVvCs-c96ai3}f>$`pc literal 0 HcmV?d00001 diff --git a/extra/images/testing/cube.tiff b/extra/images/testing/cube.tiff new file mode 100644 index 0000000000000000000000000000000000000000..eef52e32d8a421eabd24f55e70cf24d0ded05318 GIT binary patch literal 936 zcmebD)MD^qVqj>nf8fBOBF4+!!g1<=@EMV%tiFyRx>tk$e&y(KQ}F+Bro;#STWkOHKNB1>6sB;tXoCFWTDuhbJI~ zVa|gU2Mp|@*w{mb4tFTr(6nf_n}5NHX&J}IeXVU5+^vOL7%aMY4lo$7urbsyID|S# z$Z<$qXApC#P*C8ss5{kn`iIiT<1Cy!4;mao1W)XJJjYdS0-GAgd(Q3${mWYf-xMpX z-Vw#Uz2O#zbhC$|Q`Uku##2p<51X&CEM(Vg_|cji{i_diy@+UMLZ=@@>-h4+n_Ezhk-eJ^NQCD3;{+>p;uct801WzoXOt&de?@`nNM^b<`{4|q}Vkm331u3 zYGBBj6wLm+?3JQu;Iedq(m$IM&b>I0=D?tw#bu_jJaCqS{KL1KninXDFfcGOGcYnR zFbDuKBNCel$YueGIWaRZFhj*zfqXV78<+qYxS(v1eqKf<2B2vSUO@GNj4WU?Ie>g2 zBsHQ?HppBtD7yj37Kf^L0U9dB$O_gQ2Q)_-%1#5aWsuC#fU<$Y42D2^t$=hMkYfuK z2f57)Y9^4$5QHQS;&VV@QE6UYVp2}3ZdrbEVv=4-W?CA91J`vepb|zH?U|dHp6Z*J yo|&AjV5VoHXP{tUu5W0hZ(yMiXlSO8QBqQ1rLSLJUapr3QVGOBm3sL_=?nmz#2CZ? literal 0 HcmV?d00001 diff --git a/extra/images/testing/noise.tiff b/extra/images/testing/noise.tiff new file mode 100644 index 0000000000000000000000000000000000000000..2958b0b838ee75c052dbacdc768bfe01dcb4de6f GIT binary patch literal 83548 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%0Ap;E`Y1%DWyoOzUpWNN!a5*0(zRjeD-!XHD` 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 fvQcjU!29|GoYn%#+Gw literal 0 HcmV?d00001 diff --git a/extra/images/testing/small.tiff b/extra/images/testing/small.tiff new file mode 100644 index 0000000000000000000000000000000000000000..7051d58218a7f1db0b7bc72983e76eae61c81ecf GIT binary patch literal 27604 zcmeG_33yY*)^n3IZPJ#ml&!#}EG>{WTeH%Zrb$Z!EiG*+izuYYZ5o=5EVKnwL_`Ii zC?c{cf;?nV@aba_QCY=>2Z93bugF6{QQ;w?g7BZYNt!e*mAt3_hrZnQPR^V;=ggTi zXU@#c?M$`WK`06#)CFBfEJOfAUj&neBOpTrzzl$yfJ*~HW&ob;g&9zmKzQWLu16@* zi^p<1dtuC<62RNVOWzmp-H8ag-V9*q;e`bNrv}K$@WQw*A;Cbs;8PAXB7lbl_R;RKtH%*7@RsxLC{4|m#an8U5nm+A1d2>&lhvhb%(OcDdW87oXeg)NVY0hSP&JmTtFgIVxoK&* zqL$0kID@{W9=qFNrfV_i`IMQmP*#_dFU%71TZ98|jzO=iwK*(0SDwkDGg5q)skSzU z@5g%U$l>FP0~DcKi3J4Ve@($goxMdvt22x4i7dU%!iNITPS9FAKq}P^O6RgUs%$oM zUWZPTgJrZL_ZujurHIn#TvQ(RuT1zANvd$~K_5akTPY9(2=Pz`$tblMOtnqz$-q7u zC?kL}SO$2VLFdxll9WK&-=Q-~OFQlki-q5wpE+F>l;5S>?MP=;lbzyMP)?iMp{Ib{ z@736EphHSal^UDgjXhJV$#c6+2Blc4R;e^%QISd|(G&`WDuGa{7Ryv(jX*Az%Eff| zwB~DVTqx68oi3eKPx)jPWQ$}461DW#WCrlHk*T2^rm2*n$YHb49&Xn;oD^j+VpM-4TZZ>Br)}FuQ4b2-a}%g*f%H1{Mdij_a5;usE32&pFy^ za^;DrcxS{k#Yi$M9LFMWN^#jR`5hp5lR)+HuD2H2|Efx(C7YO7)5{@PlsHU!t<^<2fZ6N*KCiktMX)T75wSVTO^uW}4`L^+Q!qZ5 zuiUJ&>KtmDwbo>GJ6aFE_R75czu5|bYBb#>Qs9!dkpg3tbQ8yFOfZ-DjNL9ipt1h) zaW~-$zz6fGZDyOJ5|*PB4otMD08}t_s}Tj$m^7Q0El@}WbPA(bCKP#65smSrV!%s; z9-3?(=91AVjCdH$fPoY+*>Z`1PGPhI3}YQqhz>F_#15PSJ){bp%7GG6T$dOk2~Ke> zVu&y}1zt#LUWhN*!fYtTDQF9+2P30lIZhRzC!{ouLISW%gz++nMUXCQdQ0 zLXLSAa!jL;V;Y5mrcnrK7)w*o(i93@qk@*Hz?z6)6~Ke2#)C-6lk#LdIZwd@(+Gt; zp@=6G^Mu(vh;l$Al<^=W!a7|9ffC{*gir}YQwW9-Hz6Djk!z&c0apM2!|lF@p7tlimg2U)-cqEby$zO_6du>@p#yx-U#wND z%N@2_69iXh#jpZWTd0-_6;f%YC=1-UUAlJV6q}r|ByZyTLT&ql--CsxAb1Mnw>*aV zcs#NB_e%Y0|6VnK+;Al!88>XMu(?9AKuE-4?+TkMBnyN@9QLlTxk9o)NW@|93Y#k= z3xq@*_O7tGLb5ghU+nuCTd6vOq}0Vebl?D%_6u(?9AKuE-4 z?+TkMBnyN@9QLlTxk9o)NW@|93Y#k=3xq@*_O7tGLb5ghU+nuCTd6 zvOq}0Vebl?D%_6u(?9AKuE-4?+TkMBnyN@9QLlTxk9o)NW>j_7uUbBnXBblK8;R|(`)tH+$x~eEsPH&LzqbXBYjVmuC?PmD1ezJUYL5WsP zre*T^Dm!e$;`23C8nV1ZTUkZo4Z8fok!fV$W@a6xqWACup_30A4D6J{)l>q)GJzq> z;4;A8$Q+OKR_XxOVA8wtxSX7Nswoe)0cUxH*&VPCJgXeGnibKTle5Z8i){|mG#l&& zG{eTa96qK8HZObo{4NvTEuN+KY#{dWaA0R9h&KjQH`!L>q0QlY%eb5lMVefcMdfhl zn*2g_I14HPqfe=u$oTbUVIiQjIZp=LzGwi;?NB4w^6H|B%sOL>MnDfnq>Pj!4>myh zKw3e*Ps1F(Pi@%qeXBhYuq)G7Sxb}K?Z-btKeW>*Y;m92V0VlUyq&tDQm-?EEB|@A zA*ewu0|D%R+l_;2h;krujB7cW}qHTJ5O}lif}iWx&V^O7Hq3dj5abK-^IQ;|X%U z^;ED^cU*P&^vK#)SKkw2u&NzN3Dnfb z-&RrIcjm#G`V@t8e(?AFd0hHk!8e}w2&fo73Hb4T((fGx&}E(Ilum}u^uCdZrL*|G zL?cbvttH=!mU@ZKbLbQj!t)-3OXuMfSxl22Pv^MwakUsEXwJ(twixkiYoWvpDVxR+ zUd*bc%UUoZ%!S57>Z6g+m`p#0miZ&(5wat6quq;n78kc$bmPA%!*#Rp(ZC4dwnDoZ z`JUTrMJZqrS_ikc9A~X=vl%{UzG{clYPMO8kOyvrD66if2F^jnFvse)6i{$>D-}Qy z1xQsbeB}^;LzQ-YpcJmfWG#b}%S^5&FBR@Ru0_|_8jG!q3e470FI6Pn9x=u;aL>2Z zneN{pojJ7*_=cy_McK=0ChLKXZb(Zr@jEWu5(*BQv-z~fhY&{_D60)L^`jYMF)b~w z0^W5PZ0?$TIC$rnG{VR4F9j%&z*o?Iv)FJ1v(?~i?IIO=mk`)!)vMr0 zYp+#lNhMUR&&qVZz-6;|%dsk`!dT}k$9cBXY|>Lc!w{|NiJ;4XC=u>RI-}JD)D>xT z*xdG3C^o!2g(I^m7-_&0Q&@?m!z(YqK|WUJcG-qeRy+tP10WDS+15+U^$=qWF40NHZVFl?RKRi!hw!gbP9W^)zQ=+Ziit4d3-<6?nP zK6EQ$PMys$O=UJ2eU^&zG#=hPd(glcX`pI#ZnFzO&Qy2<)DAiR=AK5kCXcQ$(x>(X z7&pNqzhGEP0kEKvHY>I@$7Qp_NOw{`$G~M=GdNNkY)p;K1rF5)9}Qhx7feo%stha) z!71&C7Dp@8p3ff=~pO7 zq(+qpPa{}fOmT_HYGc44vshgYd?ex6apTFzL-5BB92oB~oOF7py|l6jdjf*v3Q2r! zA%?vd2>Ic-$4B1JEG{QWU%@SYCg|-B=u4s;=*0#&XdU3$0Gk_Jc8tfiNv^5KFg@!f zJ3v8zyJEc21M_Ga51fx-gT)HNh=Fys8!QG4?*sUusct;Hm?Z$unQEdM0R8~rerC7D z1TbEwBwHvQ#9J2bnSL%xUk9)N;21|$g&JTuhd(OD7y#DcgpjArm8%FO8!6 zj7T}?$N4$^INNHN1z=6ceAJJtS&GoM2jDMUKKA4KJp;Zu8=D$rH0s{NZH8{N^BH zB}F6Vgr}i|Xz8~SPBjD(gvRtflL#YWCEUae;(lTtv5;6stRmJEe9Mpql$4S!^oJzn8ujRc!aTpv6}HbV=H4f<6XuvMl<7U#%1`gA<@hf zW*=rIQ_fT~%b4StwM+-|9_C!;V&-b*Cgu+20p|P6X6AY3Pb@YoiAAz9SwmRGtkEnz zYYOWg)_m47);iW!);`wztdpz@tm_f65vdV_Ba{)^h_MmIh=zzc5sM?%Mr?`LA8{<= zbi@yAHamr#&X%!@+2h!g+0)tc*-x=Ivv;wNuurisM@B|=iyR!88(9{qi*!ZKiCh}_ zeB>LEM3y7+tIm&d;neJLQ;Y- zp)A3YFh5~K!rKXF6C)D`Bo-zb5@#nqo47aeR1!0(Z<0DmpLBoHnxy?nr<2*q>B-vU z$;tDQUr0Wbe4%q<=j_haof|tZ@BCWl<`ibifD~eFN<>4-y zx*Y9trE8C_`CX~5bGvTtdaUcu-N%GqP zPU@Z4+thne?>BmXMRq1tq?ueo?jtYsN$peI$K7XDpF@4FrlqHiPMejsIqkE)+`jU@ zwS5=$-P89%zux^y`_1ULvERr2IsN7R>-s<0|3LpM1JVbK8SvnMZ3E7xcTLx(PfOpJ z{%J;R#?Xu@8LKls7|0$dA6P%|se$heVhoZDsvER?(BZ+1!P3E|!A}i7!i#{jnytLm zyc3x*nX1gGnHw`tW~F46WX;Ojmh~;aAAdamG5!Jmb%8`+7OW9`B1{wx7tRvy5dK>< zNTe4n7kwa(6&H!`6>k?`%pROgWj~$$p(IICDtS<{NAinQCUrm7E^0yQbiUP&Gik*s|lv1Tj`I7RxA(=z!hin*fHYYuY%2|_hDmN`xm%A#rIgiYn zl=n$Mk#%ZA@QeBX$e5tSpB zjrgKuP>G{tM=7IpcfT(N0pCSKI+Q~Va1G! z1C>dY6Dv1V{!mp=wV>*g(HW!NqxV$DRZpm1UwvhaX3WAd&13mv?;iW^xbEXj|{Lb_{8}EGkuI_i4?|N-w+(g~PmnTI`8Z&9bq-(k| z-D=(C8g0#rnhSch{z?7UhJ3>!!#OIKT1cI#&8=Nnd)An1eB5}hZfM=&y7MNrX_@Kb zmYzw$HIQPf<>JV#)=_2*+9{ z;k?ti-IWZBse|qe_XF;eQ}d=SpZa4%b;HYz360jqgH3~)9&S1_t!Ua`r?aN(r|+53 zXU2UqPTsA$d(}O}JvI02xwr4Vv+q4UbJ)yv_i^v5zwaNj1hW>-`u_eg_wSh9bN0;H zrydygz{WY9<~Zh@cyP#rD<5J#WP0e(!{UdRK74(yVeUKg1oIZpyEb1x|DC@G|FYyS zHy){dmqW|+(q9%G4YAF7iTYC`6TyA z_mj;_N|tP2+HdKjORp`fTXt-D!Sc;3Qdc~@;>uHor;a|I|MaGnJy*_K`SUZTXFgmt zY}MA)16D77mi_G1XTMxiz2?AQ<$qnjw)@(7Ykzsp`dst6igo+e%hs>okh)>PM#e_> z#&ge4c>cW?G%sx5l(}iu=9JCzUPLdtU;OHClm2%6r4cXfd0GDQroZ?9`-&||Tjp*h zwl;0Ou&r)e^Y(GukGwMcl|4I#?AY>Z)~nC$?6Y(EYn@+P@H+SP*{|PtW7->+ciDHH z-)-D|YR|+ypX?pG_k(?-_8r9FXWP5dcefst z9Nh8`$v?Ink{;T6Sax{(dy4mV9LYKI`q80BcfYTB|IH7yAG~{P=?%+`8#9OQf)z41>c(sp5{yk1#c>|kySn_Ltxa^d9#;~CHYtHRIP%m5g&aQz=j1b*cJ literal 0 HcmV?d00001 diff --git a/extra/images/testing/square.tiff b/extra/images/testing/square.tiff new file mode 100644 index 0000000000000000000000000000000000000000..16e94f70b88942617ab6b382855474b71b5580d7 GIT binary patch literal 660 zcmebD)MBtSi?d(?3*Xf zP~c!-s%A;(yc+yx)!$aPhbjhK9XbD+j8Z1-XkfT=pnm$KVhI_IDn=gxh1DN;1oD^} zn4|*BA25FS-ZU>F|Fe@)r)^80gT|?lx%Fy{JQ3?ZH#_MkxjWo{KF#^h^SG8X3z-AB zgibLm^E{@r{O7B?BKs5-5`<41{;S^Oykf(|KBH5N@dp`{#j5{uw|74E_y3i_@abIr z!c7L%S7f3XEpEoY{?V4Z;=lU~rKrzslQ=3f?Jr5~Fx$qX$uP6%hxpGVAI6-c&luS1 zGCp0eUsf?8&P2q6QJ-6L{kt^+oYs#T7*xMK{_D1A{gD<0p$&%?F!1<2XAs|9`7dOK zNB6ET4jrr2nGOmV9b`G!;l;!t!oa}D%m55L1_2;uL}D`m*(^XYBPIq0W~ewTkk1BX z1BDs5plpzSUPdOc8CF0EK}Hs^nH)fIAtW`TP&UY1F(|tM$QFmHHwGFc#mEZQ3v?WV zG?X0#WXm9#qXA_z1N9mL?X?2ZQ9w1eP;nrW!3%08kjW5)Bo5+pKw)xDVsWuB&Q>tfGto0pFfi9QG}1S)PzW?MQ^+VODX`Ml UFE20G%LJ(eVxUUB{GxOQ0QB0WtpET3 literal 0 HcmV?d00001 From 8dec2070e5a8aa497fbb7a0817f22d1ef4fc5d1f Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Fri, 25 Sep 2009 16:51:47 -0400 Subject: [PATCH 08/19] compression.lzw: supports both TIFF and GIF --- basis/compression/lzw/lzw.factor | 78 +++++++------- basis/images/tiff/tiff.factor | 5 +- extra/compression/lzw-gif/lzw-gif.factor | 126 ----------------------- extra/images/gif/gif-tests.factor | 4 +- extra/images/gif/gif.factor | 4 +- 5 files changed, 46 insertions(+), 171 deletions(-) delete mode 100644 extra/compression/lzw-gif/lzw-gif.factor diff --git a/basis/compression/lzw/lzw.factor b/basis/compression/lzw/lzw.factor index d186ad047c..9fae7f4f40 100644 --- a/basis/compression/lzw/lzw.factor +++ b/basis/compression/lzw/lzw.factor @@ -5,28 +5,38 @@ prettyprint sequences vectors ; QUALIFIED-WITH: bitstreams bs IN: compression.lzw -SYMBOL: clear-code -4 clear-code set-global +SYMBOL: current-lzw -SYMBOL: end-of-information -5 end-of-information set-global +TUPLE: lzw +input +output +table +code +old-code +initial-code-size +code-size +clear-code +end-of-information-code ; -TUPLE: lzw input output table code old-code initial-code-size code-size ; +TUPLE: tiff-lzw < lzw ; +TUPLE: gif-lzw < lzw ; : initial-uncompress-table ( -- seq ) - end-of-information get 1 + iota [ 1vector ] V{ } map-as ; + current-lzw get end-of-information-code>> 1 + + iota [ 1vector ] V{ } map-as ; : reset-lzw-uncompress ( lzw -- lzw ) initial-uncompress-table >>table dup initial-code-size>> >>code-size ; -: ( input code-size -- obj ) - lzw new - swap >>initial-code-size - dup initial-code-size>> >>code-size +: ( input code-size class -- obj ) + new + swap >>code-size + dup code-size>> >>initial-code-size + dup code-size>> 1 - 2^ >>clear-code + dup clear-code>> 1 + >>end-of-information-code swap >>input - BV{ } clone >>output - reset-lzw-uncompress ; + BV{ } clone >>output ; ERROR: not-in-table value ; @@ -45,15 +55,16 @@ ERROR: not-in-table value ; : write-code ( lzw -- ) [ lookup-code ] [ output>> ] bi push-all ; -: kdebug ( lzw -- lzw ) - dup "TIFF: incrementing code size " write - [ code-size>> pprint ] - [ " table length " write table>> length pprint ] bi - nl ; +GENERIC: code-space-full? ( lzw -- ? ) + +M: tiff-lzw code-space-full? + [ table>> length ] [ code-size>> 2^ 1 - ] bi = ; + +M: gif-lzw code-space-full? + [ table>> length ] [ code-size>> 2^ ] bi = ; : maybe-increment-code-size ( lzw -- lzw ) - dup [ table>> length ] [ code-size>> 2^ 1 - ] bi = - [ kdebug [ 1 + ] change-code-size ] when ; + dup code-space-full? [ [ 1 + ] change-code-size ] when ; : add-to-table ( seq lzw -- ) [ table>> push ] @@ -64,9 +75,8 @@ ERROR: not-in-table value ; DEFER: lzw-uncompress-char : handle-clear-code ( lzw -- ) - "CLEAR CODE" print reset-lzw-uncompress - lzw-read dup end-of-information get = [ + lzw-read dup current-lzw get end-of-information-code>> = [ 2drop ] [ >>code @@ -94,10 +104,10 @@ DEFER: lzw-uncompress-char : lzw-uncompress-char ( lzw -- ) lzw-read [ >>code - dup code>> end-of-information get = [ + dup code>> current-lzw get end-of-information-code>> = [ drop ] [ - dup code>> clear-code get = [ + dup code>> current-lzw get clear-code>> = [ handle-clear-code ] [ handle-uncompress-code @@ -108,19 +118,13 @@ DEFER: lzw-uncompress-char drop ] if* ; -: register-special-codes ( first-code-size -- first-code-size ) - [ - 1 - 2^ dup clear-code set - 1 + end-of-information set - ] keep ; +: lzw-uncompress ( bitstream code-size class -- byte-array ) + dup current-lzw [ + [ reset-lzw-uncompress drop ] [ lzw-uncompress-char ] [ output>> ] tri + ] with-variable ; -: lzw-uncompress ( bitstream code-size -- byte-array ) - register-special-codes - - [ lzw-uncompress-char ] [ output>> ] bi ; +: tiff-lzw-uncompress ( seq -- byte-array ) + bs: 9 tiff-lzw lzw-uncompress ; -: lzw-uncompress-msb0 ( seq code-size -- byte-array ) - [ bs: ] dip lzw-uncompress ; - -: lzw-uncompress-lsb0 ( seq code-size -- byte-array ) - [ bs: ] dip lzw-uncompress ; +: gif-lzw-uncompress ( seq code-size -- byte-array ) + [ bs: ] dip gif-lzw lzw-uncompress ; diff --git a/basis/images/tiff/tiff.factor b/basis/images/tiff/tiff.factor index da03f455b5..d8f7b09ed7 100755 --- a/basis/images/tiff/tiff.factor +++ b/basis/images/tiff/tiff.factor @@ -434,13 +434,10 @@ ERROR: bad-small-ifd-type n ; ERROR: unhandled-compression compression ; -: lzw-tiff-uncompress ( seq -- byte-array ) - 9 lzw-uncompress-msb0 ; - : (uncompress-strips) ( strips compression -- uncompressed-strips ) { { compression-none [ ] } - { compression-lzw [ [ lzw-tiff-uncompress ] map ] } + { compression-lzw [ [ tiff-lzw-uncompress ] map ] } [ unhandled-compression ] } case ; diff --git a/extra/compression/lzw-gif/lzw-gif.factor b/extra/compression/lzw-gif/lzw-gif.factor deleted file mode 100644 index 8961abbf44..0000000000 --- a/extra/compression/lzw-gif/lzw-gif.factor +++ /dev/null @@ -1,126 +0,0 @@ -! Copyright (C) 2009 Doug Coleman, Keith Lazuka -! See http://factorcode.org/license.txt for BSD license. -USING: accessors combinators io kernel math namespaces -prettyprint sequences vectors ; -QUALIFIED-WITH: bitstreams bs -IN: compression.lzw-gif - -SYMBOL: clear-code -4 clear-code set-global - -SYMBOL: end-of-information -5 end-of-information set-global - -TUPLE: lzw input output table code old-code initial-code-size code-size ; - -: initial-uncompress-table ( -- seq ) - end-of-information get 1 + iota [ 1vector ] V{ } map-as ; - -: reset-lzw-uncompress ( lzw -- lzw ) - initial-uncompress-table >>table - dup initial-code-size>> >>code-size ; - -: ( input code-size -- obj ) - lzw new - swap >>initial-code-size - dup initial-code-size>> >>code-size - swap >>input - BV{ } clone >>output - reset-lzw-uncompress ; - -ERROR: not-in-table value ; - -: lookup-old-code ( lzw -- vector ) - [ old-code>> ] [ table>> ] bi nth ; - -: lookup-code ( lzw -- vector ) - [ code>> ] [ table>> ] bi nth ; - -: code-in-table? ( lzw -- ? ) - [ code>> ] [ table>> length ] bi < ; - -: code>old-code ( lzw -- lzw ) - dup code>> >>old-code ; - -: write-code ( lzw -- ) - [ lookup-code ] [ output>> ] bi push-all ; - -: kdebug ( lzw -- lzw ) - dup "GIF: incrementing code size " write - [ code-size>> pprint ] - [ " table length " write table>> length pprint ] bi - nl ; - -: maybe-increment-code-size ( lzw -- lzw ) - dup [ table>> length ] [ code-size>> 2^ ] bi = - [ kdebug [ 1 + ] change-code-size ] when ; - -: add-to-table ( seq lzw -- ) - [ table>> push ] - [ maybe-increment-code-size 2drop ] 2bi ; - -: lzw-read ( lzw -- lzw n ) - [ ] [ code-size>> ] [ input>> ] tri bs:read ; - -DEFER: lzw-uncompress-char -: handle-clear-code ( lzw -- ) - "CLEAR CODE" print - reset-lzw-uncompress - lzw-read dup end-of-information get = [ - 2drop - ] [ - >>code - [ write-code ] - [ code>old-code ] bi - lzw-uncompress-char - ] if ; - -: handle-uncompress-code ( lzw -- lzw ) - dup code-in-table? [ - [ write-code ] - [ - [ - [ lookup-old-code ] - [ lookup-code first ] bi suffix - ] [ add-to-table ] bi - ] [ code>old-code ] tri - ] [ - [ - [ lookup-old-code dup first suffix ] keep - [ output>> push-all ] [ add-to-table ] 2bi - ] [ code>old-code ] bi - ] if ; - -: lzw-uncompress-char ( lzw -- ) - lzw-read [ - >>code - dup code>> end-of-information get = [ - drop - ] [ - dup code>> clear-code get = [ - handle-clear-code - ] [ - handle-uncompress-code - lzw-uncompress-char - ] if - ] if - ] [ - drop - ] if* ; - -: register-special-codes ( first-code-size -- first-code-size ) - [ - 1 - 2^ dup clear-code set - 1 + end-of-information set - ] keep ; - -: lzw-uncompress ( bitstream code-size -- byte-array ) - register-special-codes - - [ lzw-uncompress-char ] [ output>> ] bi ; - -: lzw-uncompress-msb0 ( seq code-size -- byte-array ) - [ bs: ] dip lzw-uncompress ; - -: lzw-uncompress-lsb0 ( seq code-size -- byte-array ) - [ bs: ] dip lzw-uncompress ; diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor index 629ab300d4..87ce507b2e 100644 --- a/extra/images/gif/gif-tests.factor +++ b/extra/images/gif/gif-tests.factor @@ -1,6 +1,6 @@ ! Copyright (C) 2009 Keith Lazuka. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors bitstreams compression.lzw-gif images.gif io +USING: accessors bitstreams compression.lzw images.gif io io.encodings.binary io.files kernel math math.bitwise math.parser namespaces prettyprint sequences tools.test images.viewer ; QUALIFIED-WITH: bitstreams bs @@ -49,7 +49,7 @@ IN: images.gif.tests : >index-stream ( gif -- seq ) [ compressed-bytes>> ] [ image-descriptor>> first-code-size>> ] bi - lzw-uncompress-lsb0 ; + gif-lzw-uncompress ; [ BV{ diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index 8652e049e0..c6b42a651f 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -1,6 +1,6 @@ ! Copyrigt (C) 2009 Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors arrays assocs combinators compression.lzw-gif +USING: accessors arrays assocs combinators compression.lzw constructors destructors grouping images images.loader io io.binary io.buffers io.encodings.binary io.encodings.string io.encodings.utf8 io.files io.files.info io.ports @@ -227,7 +227,7 @@ ERROR: unhandled-data byte ; : decompress ( loading-gif -- indexes ) [ compressed-bytes>> ] [ image-descriptor>> first-code-size>> ] bi - lzw-uncompress-lsb0 ; + gif-lzw-uncompress ; : colorize ( index palette transparent-index/f -- seq ) pick = [ 2drop B{ 0 0 0 0 } ] [ nth 255 suffix ] if ; From c1fbca15096e63b131e37deea05630a81061d76a Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 13:09:12 -0400 Subject: [PATCH 09/19] compression.lzw: refactored and simplified --- basis/compression/lzw/lzw.factor | 48 +++++++++++++------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/basis/compression/lzw/lzw.factor b/basis/compression/lzw/lzw.factor index 9fae7f4f40..43752584d3 100644 --- a/basis/compression/lzw/lzw.factor +++ b/basis/compression/lzw/lzw.factor @@ -5,8 +5,6 @@ prettyprint sequences vectors ; QUALIFIED-WITH: bitstreams bs IN: compression.lzw -SYMBOL: current-lzw - TUPLE: lzw input output @@ -21,12 +19,11 @@ end-of-information-code ; TUPLE: tiff-lzw < lzw ; TUPLE: gif-lzw < lzw ; -: initial-uncompress-table ( -- seq ) - current-lzw get end-of-information-code>> 1 + +: initial-uncompress-table ( size -- seq ) iota [ 1vector ] V{ } map-as ; : reset-lzw-uncompress ( lzw -- lzw ) - initial-uncompress-table >>table + dup end-of-information-code>> 1 + initial-uncompress-table >>table dup initial-code-size>> >>code-size ; : ( input code-size class -- obj ) @@ -36,7 +33,8 @@ TUPLE: gif-lzw < lzw ; dup code-size>> 1 - 2^ >>clear-code dup clear-code>> 1 + >>end-of-information-code swap >>input - BV{ } clone >>output ; + BV{ } clone >>output + reset-lzw-uncompress ; ERROR: not-in-table value ; @@ -73,17 +71,26 @@ M: gif-lzw code-space-full? : lzw-read ( lzw -- lzw n ) [ ] [ code-size>> ] [ input>> ] tri bs:read ; +: end-of-information? ( lzw code -- ? ) swap end-of-information-code>> = ; +: clear-code? ( lzw code -- ? ) swap clear-code>> = ; + +DEFER: handle-clear-code +: lzw-read* ( lzw quot: ( lzw code -- ) -- ) + [ lzw-read ] dip { + { [ 3dup drop end-of-information? ] [ 3drop ] } + { [ 3dup drop clear-code? ] [ 2drop handle-clear-code ] } + [ call( lzw code -- ) ] + } cond ; inline + DEFER: lzw-uncompress-char : handle-clear-code ( lzw -- ) reset-lzw-uncompress - lzw-read dup current-lzw get end-of-information-code>> = [ - 2drop - ] [ + [ >>code [ write-code ] [ code>old-code ] bi lzw-uncompress-char - ] if ; + ] lzw-read* ; : handle-uncompress-code ( lzw -- lzw ) dup code-in-table? [ @@ -102,26 +109,11 @@ DEFER: lzw-uncompress-char ] if ; : lzw-uncompress-char ( lzw -- ) - lzw-read [ - >>code - dup code>> current-lzw get end-of-information-code>> = [ - drop - ] [ - dup code>> current-lzw get clear-code>> = [ - handle-clear-code - ] [ - handle-uncompress-code - lzw-uncompress-char - ] if - ] if - ] [ - drop - ] if* ; + [ >>code handle-uncompress-code lzw-uncompress-char ] lzw-read* ; : lzw-uncompress ( bitstream code-size class -- byte-array ) - dup current-lzw [ - [ reset-lzw-uncompress drop ] [ lzw-uncompress-char ] [ output>> ] tri - ] with-variable ; + + [ lzw-uncompress-char ] [ output>> ] bi ; : tiff-lzw-uncompress ( seq -- byte-array ) bs: 9 tiff-lzw lzw-uncompress ; From 474ecac48f29945879fd149cee40618c182e7b08 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 13:15:58 -0400 Subject: [PATCH 10/19] images.gif: renamed loading-gif>image to gif>image to match the TIFF vocab --- extra/images/gif/gif-tests.factor | 6 +++--- extra/images/gif/gif.factor | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extra/images/gif/gif-tests.factor b/extra/images/gif/gif-tests.factor index 87ce507b2e..1eeb420a04 100644 --- a/extra/images/gif/gif-tests.factor +++ b/extra/images/gif/gif-tests.factor @@ -32,7 +32,7 @@ IN: images.gif.tests gif-example1 gif-example2 gif-example3 gif-example4 gif-example5 gif-example6 } - [ execute( -- gif ) loading-gif>image image. ] each ; + [ execute( -- gif ) gif>image image. ] each ; : declared-num-colors ( gif -- n ) flags>> 3 bits 1 + 2^ ; : actual-num-colors ( gif -- n ) global-color-table>> length ; @@ -71,7 +71,7 @@ IN: images.gif.tests 0 0 0 255 255 255 255 255 0 0 0 255 0 0 0 255 255 255 255 255 0 0 0 255 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 } -] [ gif-example3 loading-gif>image bitmap>> ] unit-test +] [ gif-example3 gif>image bitmap>> ] unit-test [ BV{ @@ -85,7 +85,7 @@ IN: images.gif.tests 255 000 000 255 000 000 000 000 000 000 000 000 255 000 000 255 } -] [ gif-example5 loading-gif>image bitmap>> ] unit-test +] [ gif-example5 gif>image bitmap>> ] unit-test [ 100 ] [ gif-example1 >index-stream length ] unit-test [ 870 ] [ gif-example2 >index-stream length ] unit-test diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index c6b42a651f..6af64be024 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -243,7 +243,7 @@ ERROR: unhandled-data byte ; [ graphic-control-extensions>> first transparent-color-index>> ] [ drop f ] if ; -: loading-gif>image ( loading-gif -- image ) +: gif>image ( loading-gif -- image ) [ ] dip [ dimensions >>dim ] [ drop RGBA >>component-order ubyte-components >>component-type ] @@ -258,4 +258,4 @@ ERROR: loading-gif-error gif-image ; dup loading?>> [ loading-gif-error ] when ; M: gif-image stream>image ( path gif-image -- image ) - drop load-gif ensure-loaded loading-gif>image ; + drop load-gif ensure-loaded gif>image ; From bdd47b99919dbf55c946349f0c206cc40d0a75ff Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Mon, 28 Sep 2009 08:47:03 -0400 Subject: [PATCH 11/19] help.markup: word link stack effect is now clickable --- basis/colors/constants/factor-colors.txt | 1 + basis/help/markup/markup.factor | 42 +++++++++---------- basis/io/styles/styles.factor | 15 +++++-- .../prettyprint/stylesheet/stylesheet.factor | 3 +- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/basis/colors/constants/factor-colors.txt b/basis/colors/constants/factor-colors.txt index b8af9d3949..64a857a2a4 100644 --- a/basis/colors/constants/factor-colors.txt +++ b/basis/colors/constants/factor-colors.txt @@ -4,3 +4,4 @@ 172 167 147 FactorDarkTan 81 91 105 FactorLightSlateBlue 55 62 72 FactorDarkSlateBlue + 0 51 0 FactorDarkGreen diff --git a/basis/help/markup/markup.factor b/basis/help/markup/markup.factor index 0201e86b3f..2377a6753a 100644 --- a/basis/help/markup/markup.factor +++ b/basis/help/markup/markup.factor @@ -1,6 +1,6 @@ ! Copyright (C) 2005, 2009 Slava Pestov. ! See http://factorcode.org/license.txt for BSD license. -USING: accessors arrays assocs classes colors.constants +USING: accessors arrays assocs classes colors colors.constants combinators definitions definitions.icons effects fry generic hashtables help.stylesheet help.topics io io.styles kernel make math namespaces parser present prettyprint @@ -154,6 +154,9 @@ ALIAS: $slot $snippet 1array \ $image prefix ; ! Some links + +string ] [ effect-style ] bi - [ write ] with-style - ] [ drop ] if ; +GENERIC: link-long-text ( topic -- ) -: inter-cleave ( x seq between -- ) - [ [ call( x -- ) ] with ] dip swap interleave ; inline +M: topic link-long-text + [ article-title ] keep write-link ; -: (($link)) ( topic words -- ) - [ dup topic? [ >link ] unless ] dip - [ [ bl ] inter-cleave ] ($span) ; inline +M: word link-long-text + dup presented associate [ + [ article-name link-style get format ] + [ drop bl ] + [ stack-effect effect>string stack-effect-style get format ] + tri + ] with-nesting ; -: ($link) ( topic -- ) - { [ link-text ] } (($link)) ; +: >topic ( obj -- topic ) dup topic? [ >link ] unless ; +PRIVATE> + +: ($link) ( topic -- ) >topic link-text ; : $link ( element -- ) first ($link) ; -: ($long-link) ( topic -- ) - { [ link-text ] [ link-effect ] } (($link)) ; - +: ($long-link) ( topic -- ) >topic link-long-text ; : $long-link ( element -- ) first ($long-link) ; : ($pretty-link) ( topic -- ) - { [ link-icon ] [ link-text ] } (($link)) ; - + >topic [ link-icon ] [ drop bl ] [ link-text ] tri ; : $pretty-link ( element -- ) first ($pretty-link) ; : ($long-pretty-link) ( topic -- ) - { [ link-icon ] [ link-text ] [ link-effect ] } (($link)) ; - -: $long-pretty-link ( element -- ) first ($long-pretty-link) ; + >topic [ link-icon ] [ drop bl ] [ link-long-text ] tri ; : <$pretty-link> ( definition -- element ) 1array \ $pretty-link prefix ; diff --git a/basis/io/styles/styles.factor b/basis/io/styles/styles.factor index b141d8d2f7..ae493be849 100644 --- a/basis/io/styles/styles.factor +++ b/basis/io/styles/styles.factor @@ -1,9 +1,10 @@ ! Copyright (C) 2005, 2009 Slava Pestov. ! See http://factorcode.org/license.txt for BSD license. -USING: hashtables io io.streams.plain io.streams.string -colors summary make accessors splitting math.order -kernel namespaces assocs destructors strings sequences -present fry strings.tables delegate delegate.protocols ; +USING: accessors assocs colors colors.constants delegate +delegate.protocols destructors fry hashtables io +io.streams.plain io.streams.string kernel make math.order +namespaces present sequences splitting strings strings.tables +summary ; IN: io.styles GENERIC: stream-format ( str style stream -- ) @@ -162,3 +163,9 @@ M: input summary : write-object ( str obj -- ) presented associate format ; : write-image ( image -- ) [ "" ] dip image associate format ; + +SYMBOL: stack-effect-style +H{ + { foreground COLOR: FactorDarkGreen } + { font-style plain } +} stack-effect-style set-global diff --git a/basis/prettyprint/stylesheet/stylesheet.factor b/basis/prettyprint/stylesheet/stylesheet.factor index 580049160d..42a701d60f 100644 --- a/basis/prettyprint/stylesheet/stylesheet.factor +++ b/basis/prettyprint/stylesheet/stylesheet.factor @@ -43,5 +43,4 @@ PRIVATE> dim-color colored-presentation-style ; : effect-style ( effect -- style ) - 0 0.2 0 1 colored-presentation-style - { { font-style plain } } assoc-union ; + presented associate stack-effect-style get assoc-union ; From 46a768befb890be5351d26e93a8088ddf3e2ce88 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 14:46:31 -0400 Subject: [PATCH 12/19] compression.lzw: added documentation --- basis/compression/lzw/authors.txt | 3 +- basis/compression/lzw/lzw-docs.factor | 83 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 basis/compression/lzw/lzw-docs.factor diff --git a/basis/compression/lzw/authors.txt b/basis/compression/lzw/authors.txt index b4bd0e7b35..14da4319b2 100644 --- a/basis/compression/lzw/authors.txt +++ b/basis/compression/lzw/authors.txt @@ -1 +1,2 @@ -Doug Coleman \ No newline at end of file +Doug Coleman +Keith Lazuka diff --git a/basis/compression/lzw/lzw-docs.factor b/basis/compression/lzw/lzw-docs.factor new file mode 100644 index 0000000000..3e738a3649 --- /dev/null +++ b/basis/compression/lzw/lzw-docs.factor @@ -0,0 +1,83 @@ +! Copyright (C) 2009 Keith Lazuka +! See http://factorcode.org/license.txt for BSD license. +USING: bitstreams byte-arrays classes help.markup help.syntax +kernel math quotations sequences ; +IN: compression.lzw + +HELP: gif-lzw-uncompress +{ $values + { "seq" sequence } { "code-size" integer } + { "byte-array" byte-array } +} +{ $description "Decompresses a sequence of LZW-compressed bytes obtained from a GIF file." } ; + +HELP: tiff-lzw-uncompress +{ $values + { "seq" sequence } + { "byte-array" byte-array } +} +{ $description "Decompresses a sequence of LZW-compressed bytes obtained from a TIFF file." } ; + +HELP: lzw-read +{ $values + { "lzw" lzw } + { "lzw" lzw } { "n" integer } +} +{ $description "Read the next LZW code." } ; + +HELP: lzw-read* +{ $values + { "lzw" lzw } { "quot" quotation } +} +{ $description "Read the next LZW code and call " { $snippet "quot" } " with the lzw object and the LZW code only if the code is neither the Clear Code nor the End of Information Code. If it does read a Clear Code, this combinator will take care of handling the Clear Code for you." } ; + +HELP: +{ $values + { "input" bit-reader } { "code-size" "number of bits" } { "class" class } + { "obj" object } +} +{ $description "Instantiate a new LZW decompressor." } ; + +HELP: code-space-full? +{ $values + { "lzw" lzw } + { "?" boolean } +} +{ $description "Determines when to increment the variable length code's bit-width." } ; + +HELP: reset-lzw-uncompress +{ $values + { "lzw" lzw } + { "lzw" lzw } +} +{ $description "Reset the LZW uncompressor state (either at initialization time or immediately after receiving a Clear Code). " } ; + +ARTICLE: "compression.lzw.differences" "LZW Differences between TIFF and GIF" +{ $vocab-link "compression.lzw" } +$nl +"There are some subtle differences between the LZW algorithm used by TIFF and GIF images." +{ $heading "Variable Length Codes" } +"Both TIFF and GIF use a variation of the LZW algorithm that uses variable length codes. In both cases, the maximum code size is 12 bits. The initial code size, however, is different between the two formats. TIFF's initial code size is always 9 bits. GIF's initial code size is specified on a per-file basis at the beginning of the image descriptor block, with a minimum of 3 bits." +$nl +"TIFF and GIF each switch to the next code size using slightly different algorithms. GIF increments the code size as soon as the LZW string table's length is equal to 2**code-size, while TIFF increments the code size when the table's length is equal to 2**code-size - 1." +{ $heading "Packing Bits into Bytes" } +"TIFF and GIF LZW algorithms differ in how they pack the code bits into the byte stream. The least significant bit in a TIFF code is stored in the most significant bit of the bytestream, while the least significant bit in a GIF code is stored in the least significant bit of the bytestream." +{ $heading "Special Codes" } +"TIFF and GIF both add the concept of a 'Clear Code' and a 'End of Information Code' to the LZW algorithm. In both cases, the 'Clear Code' is equal to 2**(code-size - 1) and the 'End of Information Code' is equal to the Clear Code + 1. These 2 codes are reserved in the string table. So in both cases, the LZW string table is initialized to have a length equal to the End of Information Code + 1." +; + +ARTICLE: "compression.lzw" "LZW Compression" +{ $vocab-link "compression.lzw" } +$nl +"Implements both the TIFF and GIF variations of the LZW algorithm." +{ $heading "Decompression" } +{ $subsection tiff-lzw-uncompress } +{ $subsection gif-lzw-uncompress } +{ $heading "Compression" } +"Compression has not yet been implemented." +$nl +"Implementation details:" +{ $subsection "compression.lzw.differences" } +; + +ABOUT: "compression.lzw" From e7db217c1ff74b5dd1b225934fa55700cfb7b631 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 14:52:00 -0400 Subject: [PATCH 13/19] images.gif: added documentation --- extra/images/gif/authors.txt | 2 ++ extra/images/gif/gif-docs.factor | 12 ++++++++++++ extra/images/gif/gif.factor | 2 +- extra/images/gif/summary.txt | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 extra/images/gif/authors.txt create mode 100644 extra/images/gif/gif-docs.factor create mode 100644 extra/images/gif/summary.txt diff --git a/extra/images/gif/authors.txt b/extra/images/gif/authors.txt new file mode 100644 index 0000000000..14da4319b2 --- /dev/null +++ b/extra/images/gif/authors.txt @@ -0,0 +1,2 @@ +Doug Coleman +Keith Lazuka diff --git a/extra/images/gif/gif-docs.factor b/extra/images/gif/gif-docs.factor new file mode 100644 index 0000000000..935e8f6beb --- /dev/null +++ b/extra/images/gif/gif-docs.factor @@ -0,0 +1,12 @@ +! Copyright (C) 2009 Keith Lazuka. +! See http://factorcode.org/license.txt for BSD license. +USING: help.markup help.syntax kernel sequences ; +IN: images.gif + +ARTICLE: "images.gif" "GIF Image Loader" +{ $vocab-link "images.gif" } +$nl +{ $notes "Currently multi-frame GIF images are not supported." } +; + +ABOUT: "images.gif" diff --git a/extra/images/gif/gif.factor b/extra/images/gif/gif.factor index 6af64be024..7301cc984f 100644 --- a/extra/images/gif/gif.factor +++ b/extra/images/gif/gif.factor @@ -1,4 +1,4 @@ -! Copyrigt (C) 2009 Doug Coleman. +! Copyrigt (C) 2009 Doug Coleman, Keith Lazuka ! See http://factorcode.org/license.txt for BSD license. USING: accessors arrays assocs combinators compression.lzw constructors destructors grouping images images.loader io diff --git a/extra/images/gif/summary.txt b/extra/images/gif/summary.txt new file mode 100644 index 0000000000..ff8fc71264 --- /dev/null +++ b/extra/images/gif/summary.txt @@ -0,0 +1 @@ +GIF image file format From f5c4fbb10c9bc0038639f82e98fb8c9319ed92f7 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 15:17:52 -0400 Subject: [PATCH 14/19] compression.lzw: better naming --- basis/compression/lzw/lzw-docs.factor | 4 ++-- basis/compression/lzw/lzw.factor | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/basis/compression/lzw/lzw-docs.factor b/basis/compression/lzw/lzw-docs.factor index 3e738a3649..c43a2d5a37 100644 --- a/basis/compression/lzw/lzw-docs.factor +++ b/basis/compression/lzw/lzw-docs.factor @@ -25,11 +25,11 @@ HELP: lzw-read } { $description "Read the next LZW code." } ; -HELP: lzw-read* +HELP: lzw-process-next-code { $values { "lzw" lzw } { "quot" quotation } } -{ $description "Read the next LZW code and call " { $snippet "quot" } " with the lzw object and the LZW code only if the code is neither the Clear Code nor the End of Information Code. If it does read a Clear Code, this combinator will take care of handling the Clear Code for you." } ; +{ $description "Read the next LZW code and, assuming that the code is neither the Clear Code nor the End of Information Code, conditionally processes it by calling " { $snippet "quot" } " with the lzw object and the LZW code. If it does read a Clear Code, this combinator will take care of handling the Clear Code for you." } ; HELP: { $values diff --git a/basis/compression/lzw/lzw.factor b/basis/compression/lzw/lzw.factor index 43752584d3..72de6f828c 100644 --- a/basis/compression/lzw/lzw.factor +++ b/basis/compression/lzw/lzw.factor @@ -75,7 +75,7 @@ M: gif-lzw code-space-full? : clear-code? ( lzw code -- ? ) swap clear-code>> = ; DEFER: handle-clear-code -: lzw-read* ( lzw quot: ( lzw code -- ) -- ) +: lzw-process-next-code ( lzw quot: ( lzw code -- ) -- ) [ lzw-read ] dip { { [ 3dup drop end-of-information? ] [ 3drop ] } { [ 3dup drop clear-code? ] [ 2drop handle-clear-code ] } @@ -90,7 +90,7 @@ DEFER: lzw-uncompress-char [ write-code ] [ code>old-code ] bi lzw-uncompress-char - ] lzw-read* ; + ] lzw-process-next-code ; : handle-uncompress-code ( lzw -- lzw ) dup code-in-table? [ @@ -109,7 +109,8 @@ DEFER: lzw-uncompress-char ] if ; : lzw-uncompress-char ( lzw -- ) - [ >>code handle-uncompress-code lzw-uncompress-char ] lzw-read* ; + [ >>code handle-uncompress-code lzw-uncompress-char ] + lzw-process-next-code ; : lzw-uncompress ( bitstream code-size class -- byte-array ) From 47c2864de3711f780694bc4cf955b730ee4985f9 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Sat, 26 Sep 2009 22:09:58 -0400 Subject: [PATCH 15/19] compression.lzw: additional refactoring --- basis/compression/lzw/lzw.factor | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/basis/compression/lzw/lzw.factor b/basis/compression/lzw/lzw.factor index 72de6f828c..e017636009 100644 --- a/basis/compression/lzw/lzw.factor +++ b/basis/compression/lzw/lzw.factor @@ -55,11 +55,10 @@ ERROR: not-in-table value ; GENERIC: code-space-full? ( lzw -- ? ) -M: tiff-lzw code-space-full? - [ table>> length ] [ code-size>> 2^ 1 - ] bi = ; +: size-and-limit ( lzw -- m n ) [ table>> length ] [ code-size>> 2^ ] bi ; -M: gif-lzw code-space-full? - [ table>> length ] [ code-size>> 2^ ] bi = ; +M: tiff-lzw code-space-full? size-and-limit 1 - = ; +M: gif-lzw code-space-full? size-and-limit = ; : maybe-increment-code-size ( lzw -- lzw ) dup code-space-full? [ [ 1 + ] change-code-size ] when ; From 1da18d06b1aca71bf68b730e340d7c876bc87585 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 30 Sep 2009 02:18:29 -0500 Subject: [PATCH 16/19] compiler.cfg.value-numbering: add some more rewrite rules, neg/neg, not/not, and a few for SIMD --- basis/compiler/cfg/hats/hats.factor | 1 + .../cfg/instructions/instructions.factor | 4 + .../representations/representations.factor | 5 +- .../expressions/expressions.factor | 18 +- .../value-numbering/rewrite/rewrite.factor | 68 +++++++- .../value-numbering/simplify/simplify.factor | 27 +++ .../value-numbering-tests.factor | 164 ++++++++++++++++-- basis/compiler/codegen/codegen.factor | 1 + 8 files changed, 252 insertions(+), 36 deletions(-) diff --git a/basis/compiler/cfg/hats/hats.factor b/basis/compiler/cfg/hats/hats.factor index 4bfcb3dac8..cf5c0095ca 100644 --- a/basis/compiler/cfg/hats/hats.factor +++ b/basis/compiler/cfg/hats/hats.factor @@ -45,6 +45,7 @@ insn-classes get [ [ next-vreg dup ] dip { { [ dup not ] [ drop \ f tag-number ##load-immediate ] } { [ dup fixnum? ] [ tag-fixnum ##load-immediate ] } + { [ dup float? ] [ ##load-constant ] } [ ##load-reference ] } cond ; diff --git a/basis/compiler/cfg/instructions/instructions.factor b/basis/compiler/cfg/instructions/instructions.factor index 716ae46592..97bdccf045 100644 --- a/basis/compiler/cfg/instructions/instructions.factor +++ b/basis/compiler/cfg/instructions/instructions.factor @@ -29,6 +29,10 @@ INSN: ##load-reference def: dst/int-rep constant: obj ; +INSN: ##load-constant +def: dst/int-rep +constant: obj ; + INSN: ##peek def: dst/int-rep literal: loc ; diff --git a/basis/compiler/cfg/representations/representations.factor b/basis/compiler/cfg/representations/representations.factor index d9c2eab6c3..f103a0195f 100644 --- a/basis/compiler/cfg/representations/representations.factor +++ b/basis/compiler/cfg/representations/representations.factor @@ -96,9 +96,8 @@ SYMBOL: always-boxed H{ } clone [ '[ [ - dup ##load-reference? [ drop ] [ - [ _ (compute-always-boxed) ] each-def-rep - ] if + dup [ ##load-reference? ] [ ##load-constant? ] bi or + [ drop ] [ [ _ (compute-always-boxed) ] each-def-rep ] if ] each-non-phi ] each-basic-block ] keep ; diff --git a/basis/compiler/cfg/value-numbering/expressions/expressions.factor b/basis/compiler/cfg/value-numbering/expressions/expressions.factor index 03aa28d70a..0ac973a206 100644 --- a/basis/compiler/cfg/value-numbering/expressions/expressions.factor +++ b/basis/compiler/cfg/value-numbering/expressions/expressions.factor @@ -14,10 +14,10 @@ C: constant-expr M: constant-expr equal? over constant-expr? [ - { - [ [ value>> class ] bi@ = ] - [ [ value>> ] bi@ = ] - } 2&& + [ value>> ] bi@ + 2dup [ float? ] both? [ fp-bitwise= ] [ + { [ [ class ] bi@ = ] [ = ] } 2&& + ] if ] [ 2drop f ] if ; TUPLE: reference-expr < expr value ; @@ -25,13 +25,7 @@ TUPLE: reference-expr < expr value ; C: reference-expr M: reference-expr equal? - over reference-expr? [ - [ value>> ] bi@ { - { [ 2dup eq? ] [ 2drop t ] } - { [ 2dup [ float? ] both? ] [ fp-bitwise= ] } - [ 2drop f ] - } cond - ] [ 2drop f ] if ; + over reference-expr? [ [ value>> ] bi@ eq? ] [ 2drop f ] if ; : constant>vn ( constant -- vn ) expr>vn ; inline @@ -43,6 +37,8 @@ M: ##load-immediate >expr val>> ; M: ##load-reference >expr obj>> ; +M: ##load-constant >expr obj>> ; + << : input-values ( slot-specs -- slot-specs' ) diff --git a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor index e598862c2b..5759d7467a 100755 --- a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor +++ b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor @@ -2,7 +2,8 @@ ! See http://factorcode.org/license.txt for BSD license. USING: accessors combinators combinators.short-circuit arrays fry kernel layouts math namespaces sequences cpu.architecture -math.bitwise math.order classes vectors locals make +math.bitwise math.order math.vectors.simd.intrinsics classes +vectors locals make alien.c-types io.binary grouping compiler.cfg compiler.cfg.registers compiler.cfg.comparisons @@ -184,7 +185,7 @@ M: ##compare-branch rewrite : >boolean-insn ( insn ? -- insn' ) [ dst>> ] dip { - { t [ t \ ##load-reference new-insn ] } + { t [ t \ ##load-constant new-insn ] } { f [ \ f tag-number \ ##load-immediate new-insn ] } } case ; @@ -258,16 +259,23 @@ M: ##sub-imm rewrite [ sub-imm>add-imm ] } cond ; -: strength-reduce-mul ( insn -- insn' ) - [ [ dst>> ] [ src1>> ] bi ] [ src2>> log2 ] bi \ ##shl-imm new-insn ; +: mul-to-neg? ( insn -- ? ) + src2>> -1 = ; -: strength-reduce-mul? ( insn -- ? ) +: mul-to-neg ( insn -- insn' ) + [ dst>> ] [ src1>> ] bi \ ##neg new-insn ; + +: mul-to-shl? ( insn -- ? ) src2>> power-of-2? ; +: mul-to-shl ( insn -- insn' ) + [ [ dst>> ] [ src1>> ] bi ] [ src2>> log2 ] bi \ ##shl-imm new-insn ; + M: ##mul-imm rewrite { { [ dup constant-fold? ] [ constant-fold ] } - { [ dup strength-reduce-mul? ] [ strength-reduce-mul ] } + { [ dup mul-to-neg? ] [ mul-to-neg ] } + { [ dup mul-to-shl? ] [ mul-to-shl ] } { [ dup src1>> vreg>expr mul-imm-expr? ] [ \ ##mul-imm reassociate ] } [ drop f ] } cond ; @@ -338,8 +346,15 @@ M: ##add rewrite \ ##add-imm rewrite-arithmetic-commutative ; : rewrite-subtraction-identity ( insn -- insn' ) dst>> 0 \ ##load-immediate new-insn ; +: sub-to-neg? ( ##sub -- ? ) + src1>> vn>expr expr-zero? ; + +: sub-to-neg ( ##sub -- insn ) + [ dst>> ] [ src2>> ] bi \ ##neg new-insn ; + M: ##sub rewrite { + { [ dup sub-to-neg? ] [ sub-to-neg ] } { [ dup subtraction-identity? ] [ rewrite-subtraction-identity ] } [ \ ##sub-imm rewrite-arithmetic ] } cond ; @@ -375,3 +390,44 @@ M: ##sar rewrite \ ##sar-imm rewrite-arithmetic ; M: ##unbox-any-c-ptr rewrite dup src>> vreg>expr dup box-displaced-alien-expr? [ rewrite-unbox-displaced-alien ] [ 2drop f ] if ; + +! Some lame constant folding for SIMD intrinsics. Eventually this +! should be redone completely. + +: rewrite-shuffle-vector ( insn expr -- insn' ) + 2dup [ rep>> ] bi@ eq? [ + [ [ dst>> ] [ src>> vn>vreg ] bi* ] + [ [ shuffle>> ] bi@ nths ] + [ drop rep>> ] + 2tri \ ##shuffle-vector new-insn + ] [ 2drop f ] if ; + +: (fold-shuffle-vector) ( shuffle bytes -- bytes' ) + 2dup length swap length /i group nths concat ; + +: fold-shuffle-vector ( insn expr -- insn' ) + [ [ dst>> ] [ shuffle>> ] bi ] dip value>> + (fold-shuffle-vector) \ ##load-constant new-insn ; + +M: ##shuffle-vector rewrite + dup src>> vreg>expr { + { [ dup shuffle-vector-expr? ] [ rewrite-shuffle-vector ] } + { [ dup reference-expr? ] [ fold-shuffle-vector ] } + { [ dup constant-expr? ] [ fold-shuffle-vector ] } + [ 2drop f ] + } cond ; + +: (fold-scalar>vector) ( insn bytes -- insn' ) + [ [ dst>> ] [ rep>> rep-components ] bi ] dip concat + \ ##load-constant new-insn ; + +: fold-scalar>vector ( insn expr -- insn' ) + value>> over rep>> { + { float-4-rep [ float>bits 4 >le (fold-scalar>vector) ] } + { double-2-rep [ double>bits 8 >le (fold-scalar>vector) ] } + [ rep-component-type heap-size >le (fold-scalar>vector) ] + } case ; + +M: ##scalar>vector rewrite + dup src>> vreg>expr dup constant-expr? + [ fold-scalar>vector ] [ 2drop f ] if ; diff --git a/basis/compiler/cfg/value-numbering/simplify/simplify.factor b/basis/compiler/cfg/value-numbering/simplify/simplify.factor index e930bcaae9..c2026a9483 100644 --- a/basis/compiler/cfg/value-numbering/simplify/simplify.factor +++ b/basis/compiler/cfg/value-numbering/simplify/simplify.factor @@ -1,6 +1,7 @@ ! Copyright (C) 2008, 2009 Slava Pestov. ! See http://factorcode.org/license.txt for BSD license. USING: kernel accessors combinators classes math layouts +sequences math.vectors.simd.intrinsics compiler.cfg.instructions compiler.cfg.value-numbering.graph compiler.cfg.value-numbering.expressions ; @@ -22,6 +23,22 @@ M: unbox-any-c-ptr-expr simplify* simplify-unbox-alien ; : expr-one? ( expr -- ? ) T{ constant-expr f 1 } = ; inline +: expr-neg-one? ( expr -- ? ) T{ constant-expr f -1 } = ; inline + +: >unary-expr< ( expr -- in ) src>> vn>expr ; inline + +M: neg-expr simplify* + >unary-expr< { + { [ dup neg-expr? ] [ src>> ] } + [ drop f ] + } cond ; + +M: not-expr simplify* + >unary-expr< { + { [ dup not-expr? ] [ src>> ] } + [ drop f ] + } cond ; + : >binary-expr< ( expr -- in1 in2 ) [ src1>> vn>expr ] [ src2>> vn>expr ] bi ; inline @@ -113,6 +130,16 @@ M: box-displaced-alien-expr simplify* [ 2drop f ] } cond ; +M: scalar>vector-expr simplify* + src>> vn>expr { + { [ dup vector>scalar-expr? ] [ src>> ] } + [ drop f ] + } cond ; + +M: shuffle-vector-expr simplify* + [ src>> ] [ shuffle>> ] [ rep>> rep-components iota ] tri + sequence= [ drop f ] unless ; + M: expr simplify* drop f ; : simplify ( expr -- vn ) diff --git a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor index 1a28aaa969..663a2f0193 100644 --- a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor +++ b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor @@ -20,15 +20,15 @@ IN: compiler.cfg.value-numbering.tests ! Folding constants together [ { - T{ ##load-reference f 0 0.0 } - T{ ##load-reference f 1 -0.0 } + T{ ##load-constant f 0 0.0 } + T{ ##load-constant f 1 -0.0 } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } ] [ { - T{ ##load-reference f 0 0.0 } - T{ ##load-reference f 1 -0.0 } + T{ ##load-constant f 0 0.0 } + T{ ##load-constant f 1 -0.0 } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } value-numbering-step @@ -36,15 +36,15 @@ IN: compiler.cfg.value-numbering.tests [ { - T{ ##load-reference f 0 0.0 } + T{ ##load-constant f 0 0.0 } T{ ##copy f 1 0 any-rep } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } ] [ { - T{ ##load-reference f 0 0.0 } - T{ ##load-reference f 1 0.0 } + T{ ##load-constant f 0 0.0 } + T{ ##load-constant f 1 0.0 } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } value-numbering-step @@ -52,15 +52,15 @@ IN: compiler.cfg.value-numbering.tests [ { - T{ ##load-reference f 0 t } + T{ ##load-constant f 0 t } T{ ##copy f 1 0 any-rep } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } ] [ { - T{ ##load-reference f 0 t } - T{ ##load-reference f 1 t } + T{ ##load-constant f 0 t } + T{ ##load-constant f 1 t } T{ ##replace f 0 D 0 } T{ ##replace f 1 D 1 } } value-numbering-step @@ -236,6 +236,78 @@ IN: compiler.cfg.value-numbering.tests } value-numbering-step ] unit-test +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 -1 } + T{ ##neg f 2 0 } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 -1 } + T{ ##mul f 2 0 1 } + } value-numbering-step +] unit-test + +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 -1 } + T{ ##neg f 2 0 } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 -1 } + T{ ##mul f 2 1 0 } + } value-numbering-step +] unit-test + +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 0 } + T{ ##neg f 2 0 } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 0 } + T{ ##sub f 2 1 0 } + } value-numbering-step +] unit-test + +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 0 } + T{ ##neg f 2 0 } + T{ ##copy f 3 0 any-rep } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-immediate f 1 0 } + T{ ##sub f 2 1 0 } + T{ ##sub f 3 1 2 } + } value-numbering-step +] unit-test + +[ + { + T{ ##peek f 0 D 0 } + T{ ##not f 1 0 } + T{ ##copy f 2 0 any-rep } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##not f 1 0 } + T{ ##not f 2 1 } + } value-numbering-step +] unit-test + [ { T{ ##peek f 0 D 0 } @@ -947,7 +1019,7 @@ cell 8 = [ { T{ ##load-immediate f 1 1 } T{ ##load-immediate f 2 2 } - T{ ##load-reference f 3 t } + T{ ##load-constant f 3 t } } ] [ { @@ -961,7 +1033,7 @@ cell 8 = [ { T{ ##load-immediate f 1 1 } T{ ##load-immediate f 2 2 } - T{ ##load-reference f 3 t } + T{ ##load-constant f 3 t } } ] [ { @@ -1000,7 +1072,7 @@ cell 8 = [ [ { T{ ##peek f 0 D 0 } - T{ ##load-reference f 1 t } + T{ ##load-constant f 1 t } } ] [ { @@ -1024,7 +1096,7 @@ cell 8 = [ [ { T{ ##peek f 0 D 0 } - T{ ##load-reference f 1 t } + T{ ##load-constant f 1 t } } ] [ { @@ -1048,7 +1120,7 @@ cell 8 = [ [ { T{ ##peek f 0 D 0 } - T{ ##load-reference f 1 t } + T{ ##load-constant f 1 t } } ] [ { @@ -1057,6 +1129,66 @@ cell 8 = [ } value-numbering-step ] unit-test +[ + { + T{ ##vector>scalar f 1 0 float-4-rep } + T{ ##copy f 2 0 any-rep } + } +] [ + { + T{ ##vector>scalar f 1 0 float-4-rep } + T{ ##scalar>vector f 2 1 float-4-rep } + } value-numbering-step +] unit-test + +[ + { + T{ ##copy f 1 0 any-rep } + } +] [ + { + T{ ##shuffle-vector f 1 0 { 0 1 2 3 } float-4-rep } + } value-numbering-step +] unit-test + +[ + { + T{ ##shuffle-vector f 1 0 { 1 2 3 0 } float-4-rep } + T{ ##shuffle-vector f 2 0 { 0 2 3 1 } float-4-rep } + } +] [ + { + T{ ##shuffle-vector f 1 0 { 1 2 3 0 } float-4-rep } + T{ ##shuffle-vector f 2 1 { 3 1 2 0 } float-4-rep } + } value-numbering-step +] unit-test + +[ + { + T{ ##shuffle-vector f 1 0 { 1 2 3 0 } float-4-rep } + T{ ##shuffle-vector f 2 1 { 1 0 } double-2-rep } + } +] [ + { + T{ ##shuffle-vector f 1 0 { 1 2 3 0 } float-4-rep } + T{ ##shuffle-vector f 2 1 { 1 0 } double-2-rep } + } value-numbering-step +] unit-test + +[ + { + T{ ##load-constant f 0 1.25 } + T{ ##load-constant f 1 B{ 0 0 160 63 0 0 160 63 0 0 160 63 0 0 160 63 } } + T{ ##copy f 2 1 any-rep } + } +] [ + { + T{ ##load-constant f 0 1.25 } + T{ ##scalar>vector f 1 0 float-4-rep } + T{ ##shuffle-vector f 2 1 { 0 0 0 0 } float-4-rep } + } value-numbering-step +] unit-test + : test-branch-folding ( insns -- insns' n ) [ V{ 0 1 } clone >>successors basic-block set value-numbering-step ] keep @@ -1203,7 +1335,7 @@ cell 8 = [ [ { T{ ##peek f 0 D 0 } - T{ ##load-reference f 1 t } + T{ ##load-constant f 1 t } T{ ##branch } } 0 diff --git a/basis/compiler/codegen/codegen.factor b/basis/compiler/codegen/codegen.factor index 8e99f79b36..b0307f685d 100755 --- a/basis/compiler/codegen/codegen.factor +++ b/basis/compiler/codegen/codegen.factor @@ -110,6 +110,7 @@ SYNTAX: CODEGEN: CODEGEN: ##load-immediate %load-immediate CODEGEN: ##load-reference %load-reference +CODEGEN: ##load-constant %load-reference CODEGEN: ##peek %peek CODEGEN: ##replace %replace CODEGEN: ##inc-d %inc-d From cdc7b7e2c75546340bbbeab27a5fbd00f5c429e1 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 30 Sep 2009 05:00:36 -0500 Subject: [PATCH 17/19] Various minor compiler tweaks: Combine address calculation with dereferencing in alien accessors; convert SIMD XOR of a vector with itself into an XOR of the destination with itself; convert SIMD unbox of zero vector into XOR of the destination with itself; fix SIMD indexing on x86-64 --- .../cfg/instructions/instructions.factor | 57 +++++++++++++------ .../cfg/intrinsics/alien/alien.factor | 8 +-- .../representations/representations.factor | 23 +++++++- .../value-numbering/rewrite/rewrite.factor | 27 +++++++++ .../value-numbering-tests.factor | 10 ++++ basis/cpu/architecture/architecture.factor | 42 ++++++++------ basis/cpu/x86/x86.factor | 35 ++++++------ 7 files changed, 146 insertions(+), 56 deletions(-) diff --git a/basis/compiler/cfg/instructions/instructions.factor b/basis/compiler/cfg/instructions/instructions.factor index 97bdccf045..cf0f668db3 100644 --- a/basis/compiler/cfg/instructions/instructions.factor +++ b/basis/compiler/cfg/instructions/instructions.factor @@ -468,65 +468,88 @@ use: src/int-rep ; ! Alien accessors INSN: ##alien-unsigned-1 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-unsigned-2 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-unsigned-4 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-signed-1 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-signed-2 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-signed-4 def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-cell def: dst/int-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-float def: dst/float-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-double def: dst/double-rep -use: src/int-rep ; +use: src/int-rep +literal: offset ; INSN: ##alien-vector def: dst use: src/int-rep -literal: rep ; +literal: offset rep ; INSN: ##set-alien-integer-1 -use: src/int-rep value/int-rep ; +use: src/int-rep +literal: offset +use: value/int-rep ; INSN: ##set-alien-integer-2 -use: src/int-rep value/int-rep ; +use: src/int-rep +literal: offset +use: value/int-rep ; INSN: ##set-alien-integer-4 -use: src/int-rep value/int-rep ; +use: src/int-rep +literal: offset +use: value/int-rep ; INSN: ##set-alien-cell -use: src/int-rep value/int-rep ; +use: src/int-rep +literal: offset +use: value/int-rep ; INSN: ##set-alien-float -use: src/int-rep value/float-rep ; +use: src/int-rep +literal: offset +use: value/float-rep ; INSN: ##set-alien-double -use: src/int-rep value/double-rep ; +use: src/int-rep +literal: offset +use: value/double-rep ; INSN: ##set-alien-vector -use: src/int-rep value +use: src/int-rep +literal: offset +use: value literal: rep ; ! Memory allocation diff --git a/basis/compiler/cfg/intrinsics/alien/alien.factor b/basis/compiler/cfg/intrinsics/alien/alien.factor index 2b903813a0..bc6baa21b7 100644 --- a/basis/compiler/cfg/intrinsics/alien/alien.factor +++ b/basis/compiler/cfg/intrinsics/alien/alien.factor @@ -33,10 +33,10 @@ IN: compiler.cfg.intrinsics.alien [ second class>> fixnum class<= ] bi and ; -: prepare-alien-accessor ( info -- offset-vreg ) - class>> [ 2inputs ^^untag-fixnum swap ] dip ^^unbox-c-ptr ^^add ; +: prepare-alien-accessor ( info -- ptr-vreg offset ) + class>> [ 2inputs ^^untag-fixnum swap ] dip ^^unbox-c-ptr ^^add 0 ; -: prepare-alien-getter ( infos -- offset-vreg ) +: prepare-alien-getter ( infos -- ptr-vreg offset ) first prepare-alien-accessor ; : inline-alien-getter ( node quot -- ) @@ -49,7 +49,7 @@ IN: compiler.cfg.intrinsics.alien [ third class>> fixnum class<= ] tri and and ; -: prepare-alien-setter ( infos -- offset-vreg ) +: prepare-alien-setter ( infos -- ptr-vreg offset ) second prepare-alien-accessor ; : inline-alien-integer-setter ( node quot -- ) diff --git a/basis/compiler/cfg/representations/representations.factor b/basis/compiler/cfg/representations/representations.factor index f103a0195f..423f415742 100644 --- a/basis/compiler/cfg/representations/representations.factor +++ b/basis/compiler/cfg/representations/representations.factor @@ -1,8 +1,8 @@ ! Copyright (C) 2009 Slava Pestov ! See http://factorcode.org/license.txt for BSD license. USING: kernel fry accessors sequences assocs sets namespaces -arrays combinators make locals deques dlists layouts -cpu.architecture compiler.utilities +arrays combinators combinators.short-circuit make locals deques +dlists layouts cpu.architecture compiler.utilities compiler.cfg compiler.cfg.rpo compiler.cfg.hats @@ -208,6 +208,25 @@ SYMBOL: phi-mappings M: ##phi conversions-for-insn [ , ] [ [ inputs>> values ] [ dst>> ] bi phi-mappings get set-at ] bi ; +! When a literal zero vector is unboxed, we replace the ##load-reference +! with a ##zero-vector instruction since this is more efficient. +: convert-to-zero-vector? ( insn -- ? ) + { + [ dst>> rep-of vector-rep? ] + [ obj>> B{ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } = ] + } 1&& ; + +: convert-to-zero-vector ( insn -- ) + dst>> dup rep-of ##zero-vector ; + +M: ##load-reference conversions-for-insn + dup convert-to-zero-vector? + [ convert-to-zero-vector ] [ call-next-method ] if ; + +M: ##load-constant conversions-for-insn + dup convert-to-zero-vector? + [ convert-to-zero-vector ] [ call-next-method ] if ; + M: vreg-insn conversions-for-insn [ compute-renaming-set ] [ perform-renaming ] bi ; diff --git a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor index 5759d7467a..d38c25a841 100755 --- a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor +++ b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor @@ -391,6 +391,29 @@ M: ##unbox-any-c-ptr rewrite dup src>> vreg>expr dup box-displaced-alien-expr? [ rewrite-unbox-displaced-alien ] [ 2drop f ] if ; +! More efficient addressing for alien intrinsics +: rewrite-alien-addressing ( insn -- insn' ) + dup src>> vreg>expr dup add-imm-expr? [ + [ src1>> vn>vreg ] [ src2>> vn>constant ] bi + [ >>src ] [ '[ _ + ] change-offset ] bi* + ] [ 2drop f ] if ; + +M: ##alien-unsigned-1 rewrite rewrite-alien-addressing ; +M: ##alien-unsigned-2 rewrite rewrite-alien-addressing ; +M: ##alien-unsigned-4 rewrite rewrite-alien-addressing ; +M: ##alien-signed-1 rewrite rewrite-alien-addressing ; +M: ##alien-signed-2 rewrite rewrite-alien-addressing ; +M: ##alien-signed-4 rewrite rewrite-alien-addressing ; +M: ##alien-float rewrite rewrite-alien-addressing ; +M: ##alien-double rewrite rewrite-alien-addressing ; +M: ##alien-vector rewrite rewrite-alien-addressing ; +M: ##set-alien-integer-1 rewrite rewrite-alien-addressing ; +M: ##set-alien-integer-2 rewrite rewrite-alien-addressing ; +M: ##set-alien-integer-4 rewrite rewrite-alien-addressing ; +M: ##set-alien-float rewrite rewrite-alien-addressing ; +M: ##set-alien-double rewrite rewrite-alien-addressing ; +M: ##set-alien-vector rewrite rewrite-alien-addressing ; + ! Some lame constant folding for SIMD intrinsics. Eventually this ! should be redone completely. @@ -431,3 +454,7 @@ M: ##shuffle-vector rewrite M: ##scalar>vector rewrite dup src>> vreg>expr dup constant-expr? [ fold-scalar>vector ] [ 2drop f ] if ; + +M: ##xor-vector rewrite + dup [ src1>> vreg>vn ] [ src2>> vreg>vn ] bi eq? + [ [ dst>> ] [ rep>> ] bi \ ##zero-vector new-insn ] [ drop f ] if ; diff --git a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor index 663a2f0193..7751cba039 100644 --- a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor +++ b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor @@ -1189,6 +1189,16 @@ cell 8 = [ } value-numbering-step ] unit-test +[ + { + T{ ##zero-vector f 2 float-4-rep } + } +] [ + { + T{ ##xor-vector f 2 1 1 float-4-rep } + } value-numbering-step +] unit-test + : test-branch-folding ( insns -- insns' n ) [ V{ 0 1 } clone >>successors basic-block set value-numbering-step ] keep diff --git a/basis/cpu/architecture/architecture.factor b/basis/cpu/architecture/architecture.factor index 9d6f8fd662..3b1f57d08e 100644 --- a/basis/cpu/architecture/architecture.factor +++ b/basis/cpu/architecture/architecture.factor @@ -114,6 +114,14 @@ M: float-rep rep-size drop 4 ; M: double-rep rep-size drop 8 ; M: stack-params rep-size drop cell ; M: vector-rep rep-size drop 16 ; +M: char-scalar-rep rep-size drop 1 ; +M: uchar-scalar-rep rep-size drop 1 ; +M: short-scalar-rep rep-size drop 2 ; +M: ushort-scalar-rep rep-size drop 2 ; +M: int-scalar-rep rep-size drop 4 ; +M: uint-scalar-rep rep-size drop 4 ; +M: longlong-scalar-rep rep-size drop 8 ; +M: ulonglong-scalar-rep rep-size drop 8 ; GENERIC: rep-component-type ( rep -- n ) @@ -277,24 +285,24 @@ HOOK: %unbox-any-c-ptr cpu ( dst src temp -- ) HOOK: %box-alien cpu ( dst src temp -- ) HOOK: %box-displaced-alien cpu ( dst displacement base temp1 temp2 base-class -- ) -HOOK: %alien-unsigned-1 cpu ( dst src -- ) -HOOK: %alien-unsigned-2 cpu ( dst src -- ) -HOOK: %alien-unsigned-4 cpu ( dst src -- ) -HOOK: %alien-signed-1 cpu ( dst src -- ) -HOOK: %alien-signed-2 cpu ( dst src -- ) -HOOK: %alien-signed-4 cpu ( dst src -- ) -HOOK: %alien-cell cpu ( dst src -- ) -HOOK: %alien-float cpu ( dst src -- ) -HOOK: %alien-double cpu ( dst src -- ) -HOOK: %alien-vector cpu ( dst src rep -- ) +HOOK: %alien-unsigned-1 cpu ( dst src offset -- ) +HOOK: %alien-unsigned-2 cpu ( dst src offset -- ) +HOOK: %alien-unsigned-4 cpu ( dst src offset -- ) +HOOK: %alien-signed-1 cpu ( dst src offset -- ) +HOOK: %alien-signed-2 cpu ( dst src offset -- ) +HOOK: %alien-signed-4 cpu ( dst src offset -- ) +HOOK: %alien-cell cpu ( dst src offset -- ) +HOOK: %alien-float cpu ( dst src offset -- ) +HOOK: %alien-double cpu ( dst src offset -- ) +HOOK: %alien-vector cpu ( dst src offset rep -- ) -HOOK: %set-alien-integer-1 cpu ( ptr value -- ) -HOOK: %set-alien-integer-2 cpu ( ptr value -- ) -HOOK: %set-alien-integer-4 cpu ( ptr value -- ) -HOOK: %set-alien-cell cpu ( ptr value -- ) -HOOK: %set-alien-float cpu ( ptr value -- ) -HOOK: %set-alien-double cpu ( ptr value -- ) -HOOK: %set-alien-vector cpu ( ptr value rep -- ) +HOOK: %set-alien-integer-1 cpu ( ptr offset value -- ) +HOOK: %set-alien-integer-2 cpu ( ptr offset value -- ) +HOOK: %set-alien-integer-4 cpu ( ptr offset value -- ) +HOOK: %set-alien-cell cpu ( ptr offset value -- ) +HOOK: %set-alien-float cpu ( ptr offset value -- ) +HOOK: %set-alien-double cpu ( ptr offset value -- ) +HOOK: %set-alien-vector cpu ( ptr offset value rep -- ) HOOK: %alien-global cpu ( dst symbol library -- ) HOOK: %vm-field-ptr cpu ( dst fieldname -- ) diff --git a/basis/cpu/x86/x86.factor b/basis/cpu/x86/x86.factor index 14a382d6cf..eaaab19662 100644 --- a/basis/cpu/x86/x86.factor +++ b/basis/cpu/x86/x86.factor @@ -307,45 +307,45 @@ M:: x86 %set-string-nth-fast ( ch str index temp -- ) temp string-offset [+] new-ch 8-bit-version-of MOV ] with-small-register ; -:: %alien-integer-getter ( dst src size quot -- ) +:: %alien-integer-getter ( dst src offset size quot -- ) dst { src } size [| new-dst | - new-dst dup size n-bit-version-of dup src [] MOV + new-dst dup size n-bit-version-of dup src offset [+] MOV quot call dst new-dst int-rep %copy ] with-small-register ; inline -: %alien-unsigned-getter ( dst src size -- ) +: %alien-unsigned-getter ( dst src offset size -- ) [ MOVZX ] %alien-integer-getter ; inline M: x86 %alien-unsigned-1 8 %alien-unsigned-getter ; M: x86 %alien-unsigned-2 16 %alien-unsigned-getter ; M: x86 %alien-unsigned-4 32 [ 2drop ] %alien-integer-getter ; -: %alien-signed-getter ( dst src size -- ) +: %alien-signed-getter ( dst src offset size -- ) [ MOVSX ] %alien-integer-getter ; inline M: x86 %alien-signed-1 8 %alien-signed-getter ; M: x86 %alien-signed-2 16 %alien-signed-getter ; M: x86 %alien-signed-4 32 %alien-signed-getter ; -M: x86 %alien-cell [] MOV ; -M: x86 %alien-float [] MOVSS ; -M: x86 %alien-double [] MOVSD ; -M: x86 %alien-vector [ [] ] dip %copy ; +M: x86 %alien-cell [+] MOV ; +M: x86 %alien-float [+] MOVSS ; +M: x86 %alien-double [+] MOVSD ; +M: x86 %alien-vector [ [+] ] dip %copy ; -:: %alien-integer-setter ( ptr value size -- ) +:: %alien-integer-setter ( ptr offset value size -- ) value { ptr } size [| new-value | new-value value int-rep %copy - ptr [] new-value size n-bit-version-of MOV + ptr offset [+] new-value size n-bit-version-of MOV ] with-small-register ; inline M: x86 %set-alien-integer-1 8 %alien-integer-setter ; M: x86 %set-alien-integer-2 16 %alien-integer-setter ; M: x86 %set-alien-integer-4 32 %alien-integer-setter ; -M: x86 %set-alien-cell [ [] ] dip MOV ; -M: x86 %set-alien-float [ [] ] dip MOVSS ; -M: x86 %set-alien-double [ [] ] dip MOVSD ; -M: x86 %set-alien-vector [ [] ] 2dip %copy ; +M: x86 %set-alien-cell [ [+] ] dip MOV ; +M: x86 %set-alien-float [ [+] ] dip MOVSS ; +M: x86 %set-alien-double [ [+] ] dip MOVSD ; +M: x86 %set-alien-vector [ [+] ] 2dip %copy ; : shift-count? ( reg -- ? ) { ECX RCX } memq? ; @@ -1042,8 +1042,11 @@ M: x86 %shr-vector-reps { sse2? { short-8-rep ushort-8-rep int-4-rep uint-4-rep ulonglong-2-rep } } } available-reps ; -M: x86 %integer>scalar drop MOVD ; -M: x86 %scalar>integer drop MOVD ; +: scalar-sized-reg ( reg rep -- reg' ) + rep-size 8 * n-bit-version-of ; + +M: x86 %integer>scalar scalar-sized-reg MOVD ; +M: x86 %scalar>integer swap [ scalar-sized-reg ] dip MOVD ; M: x86 %vector>scalar %copy ; M: x86 %scalar>vector %copy ; From 4c856e51e1a0e43caeb12c1778b27f81e060e8fe Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 30 Sep 2009 05:00:50 -0500 Subject: [PATCH 18/19] math.matrices.simd: hack: replace 'first4' with '4 firstn' since latter is open-coded --- extra/math/matrices/simd/simd.factor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/math/matrices/simd/simd.factor b/extra/math/matrices/simd/simd.factor index 2769a783bc..014cd86265 100644 --- a/extra/math/matrices/simd/simd.factor +++ b/extra/math/matrices/simd/simd.factor @@ -20,7 +20,7 @@ M: matrix4 new-sequence 2drop matrix4 (struct) ; inline > first4 ; inline + rows>> 4 firstn ; inline :: set-rows ( c1 c2 c3 c4 c -- c ) c rows>> :> rows From b677822b76daf5b34a5788495f95ec8d0fb7c2ef Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 30 Sep 2009 05:09:20 -0500 Subject: [PATCH 19/19] compiler.cfg.value-numbering: fix overly-zealous ##compare-imm conversion --- .../value-numbering/rewrite/rewrite.factor | 1 + .../value-numbering-tests.factor | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor index d38c25a841..8e5e013606 100755 --- a/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor +++ b/basis/compiler/cfg/value-numbering/rewrite/rewrite.factor @@ -16,6 +16,7 @@ IN: compiler.cfg.value-numbering.rewrite : vreg-small-constant? ( vreg -- ? ) vreg>expr { [ constant-expr? ] + [ value>> fixnum? ] [ value>> small-enough? ] } 1&& ; diff --git a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor index 7751cba039..b2750da3fa 100644 --- a/basis/compiler/cfg/value-numbering/value-numbering-tests.factor +++ b/basis/compiler/cfg/value-numbering/value-numbering-tests.factor @@ -406,6 +406,20 @@ IN: compiler.cfg.value-numbering.tests } value-numbering-step trim-temps ] unit-test +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-constant f 1 3.5 } + T{ ##compare f 2 0 1 cc= } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-constant f 1 3.5 } + T{ ##compare f 2 0 1 cc= } + } value-numbering-step trim-temps +] unit-test + [ { T{ ##peek f 0 D 0 } @@ -434,6 +448,20 @@ IN: compiler.cfg.value-numbering.tests } value-numbering-step ] unit-test +[ + { + T{ ##peek f 0 D 0 } + T{ ##load-constant f 1 3.5 } + T{ ##compare-branch f 0 1 cc= } + } +] [ + { + T{ ##peek f 0 D 0 } + T{ ##load-constant f 1 3.5 } + T{ ##compare-branch f 0 1 cc= } + } value-numbering-step trim-temps +] unit-test + [ { T{ ##peek f 0 D 0 }