Color quantization: Difference between revisions
Content added Content deleted
(Updated D entry) |
No edit summary |
||
Line 671: | Line 671: | ||
</lang> |
</lang> |
||
=={{header|Racket}}== |
|||
<lang Racket> |
|||
#lang racket/base |
|||
(require racket/class |
|||
racket/draw) |
|||
;; This is an implementation of the Octree Quantization algorithm. This implementation |
|||
;; follows the sketch in: |
|||
;; |
|||
;; Dean Clark. Color Quantization using Octrees. Dr. Dobbs Portal, January 1, 1996. |
|||
;; http://www.ddj.com/184409805 |
|||
;; |
|||
;; This code is adapted from the color quantizer in the implementation of Racket's |
|||
;; file/gif standard library. |
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|||
;; To view an example of the quantizer, run the following test submodule |
|||
;; in DrRacket: |
|||
(module+ test |
|||
(require racket/block net/url) |
|||
(define frog |
|||
(block |
|||
(define url (string->url "http://rosettacode.org/mw/images/3/3f/Quantum_frog.png")) |
|||
(define frog-ip (get-pure-port url)) |
|||
(define bitmap (make-object bitmap% frog-ip)) |
|||
(close-input-port frog-ip) |
|||
bitmap)) |
|||
;; Display the original: |
|||
(print frog) |
|||
;; And the quantized version (16 colors): |
|||
(print (quantize-bitmap frog 16))) |
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|||
;; quantize-bitmap: bitmap positive-number -> bitmap |
|||
;; Given a bitmap, returns a new bitmap quantized to, at most, n colors. |
|||
(define (quantize-bitmap bm n) |
|||
(let* ([width (send bm get-width)] |
|||
[height (send bm get-height)] |
|||
[len (* width height 4)] |
|||
[source-buffer (make-bytes len)] |
|||
[_ (send bm get-argb-pixels 0 0 width height source-buffer)] |
|||
[an-octree (make-octree-from-argb source-buffer n)] |
|||
[dest-buffer (make-bytes len)]) |
|||
(let quantize-bitmap-loop ([i 0]) |
|||
(when (< i len) |
|||
(let* ([i+1 (+ i 1)] |
|||
[i+2 (+ i 2)] |
|||
[i+3 (+ i 3)] |
|||
[a (bytes-ref source-buffer i)] |
|||
[r (bytes-ref source-buffer i+1)] |
|||
[g (bytes-ref source-buffer i+2)] |
|||
[b (bytes-ref source-buffer i+3)]) |
|||
(cond |
|||
[(alpha-opaque? a) |
|||
(let-values ([(new-r new-g new-b) |
|||
(octree-lookup an-octree r g b)]) |
|||
(bytes-set! dest-buffer i 255) |
|||
(bytes-set! dest-buffer i+1 new-r) |
|||
(bytes-set! dest-buffer i+2 new-g) |
|||
(bytes-set! dest-buffer i+3 new-b))] |
|||
[else |
|||
(bytes-set! dest-buffer i 0) |
|||
(bytes-set! dest-buffer i+1 0) |
|||
(bytes-set! dest-buffer i+2 0) |
|||
(bytes-set! dest-buffer i+3 0)])) |
|||
(quantize-bitmap-loop (+ i 4)))) |
|||
(let* ([new-bm (make-object bitmap% width height)] |
|||
[dc (make-object bitmap-dc% new-bm)]) |
|||
(send dc set-argb-pixels 0 0 width height dest-buffer) |
|||
(send dc set-bitmap #f) |
|||
new-bm))) |
|||
;; make-octree-from-argb: bytes positive-number -> octree |
|||
;; Constructs an octree ready to quantize the colors from an-argb. |
|||
(define (make-octree-from-argb an-argb n) |
|||
(unless (> n 0) |
|||
(raise-type-error 'make-octree-from-argb "positive number" n)) |
|||
(let ([an-octree (new-octree)] |
|||
[len (bytes-length an-argb)]) |
|||
(let make-octree-loop ([i 0]) |
|||
(when (< i len) |
|||
(let ([a (bytes-ref an-argb i)] |
|||
[r (bytes-ref an-argb (+ i 1))] |
|||
[g (bytes-ref an-argb (+ i 2))] |
|||
[b (bytes-ref an-argb (+ i 3))]) |
|||
(when (alpha-opaque? a) |
|||
(octree-insert-color! an-octree r g b) |
|||
(let reduction-loop () |
|||
(when (> (octree-leaf-count an-octree) n) |
|||
(octree-reduce! an-octree) |
|||
(reduction-loop))))) |
|||
(make-octree-loop (+ i 4)))) |
|||
(octree-finalize! an-octree) |
|||
an-octree)) |
|||
;; alpha-opaque? byte -> boolean |
|||
;; Returns true if the alpha value is considered opaque. |
|||
(define (alpha-opaque? a) |
|||
(>= a 128)) |
|||
;; The maximum level height of an octree. |
|||
(define MAX-LEVEL 7) |
|||
;; A color is a (vector byte byte byte) |
|||
;; An octree is a: |
|||
(define-struct octree (root ; node |
|||
leaf-count ; number |
|||
reduction-heads ; (vectorof (or/c node #f)) |
|||
palette) ; (vectorof (or/c color #f)) |
|||
#:mutable) |
|||
;; reduction-heads is used to accelerate the search for a reduction candidate. |
|||
;; A subtree node is a: |
|||
(define-struct node (leaf? ; bool |
|||
npixels ; number -- number of pixels this subtree node represents |
|||
redsum ; number |
|||
greensum ; number |
|||
bluesum ; number |
|||
children ; (vectorof (or/c #f node)) |
|||
next ; (or/c #f node) |
|||
palette-index) ; (or/c #f byte?) |
|||
#:mutable) |
|||
;; node-next is used to accelerate the search for a reduction candidate. |
|||
;; new-octree: -> octree |
|||
(define (new-octree) |
|||
(let* ([root-node (make-node #f ;; not a leaf |
|||
0 ;; no pixels under us yet |
|||
0 ;; red sum |
|||
0 ;; green sum |
|||
0 ;; blue sum |
|||
(make-vector 8 #f) ;; no children so far |
|||
#f ;; next |
|||
#f ;; palette-index |
|||
)] |
|||
[an-octree |
|||
(make-octree root-node |
|||
0 ; no leaves so far |
|||
(make-vector (add1 MAX-LEVEL) #f) ; no reductions so far |
|||
(make-vector 256 #(0 0 0)))]) ; the palette |
|||
;; Although we'll almost never reduce to this level, initialize the first |
|||
;; reducible node to the root, for completeness sake. |
|||
(vector-set! (octree-reduction-heads an-octree) 0 root-node) |
|||
an-octree)) |
|||
;; rgb->index: natural-number byte byte byte -> octet |
|||
;; Given a level and an (r,g,b) triplet, returns an octet that can be used |
|||
;; as an index into our octree structure. |
|||
(define (rgb->index level r g b) |
|||
(bitwise-ior (bitwise-and 4 (arithmetic-shift r (- level 5))) |
|||
(bitwise-and 2 (arithmetic-shift g (- level 6))) |
|||
(bitwise-and 1 (arithmetic-shift b (- level 7))))) |
|||
;; octree-insert-color!: octree byte byte byte -> void |
|||
;; Accumulates a new r,g,b triplet into the octree. |
|||
(define (octree-insert-color! an-octree r g b) |
|||
(node-insert-color! (octree-root an-octree) an-octree r g b 0)) |
|||
;; node-insert-color!: node octree byte byte byte natural-number -> void |
|||
;; Adds a color to the node subtree. While we hit #f, we create new nodes. |
|||
;; If we hit an existing leaf, we accumulate our color into it. |
|||
(define (node-insert-color! a-node an-octree r g b level) |
|||
(let insert-color-loop ([a-node a-node] |
|||
[level level]) |
|||
(cond [(node-leaf? a-node) |
|||
;; update the leaf with the new color |
|||
(set-node-npixels! a-node (add1 (node-npixels a-node))) |
|||
(set-node-redsum! a-node (+ (node-redsum a-node) r)) |
|||
(set-node-greensum! a-node (+ (node-greensum a-node) g)) |
|||
(set-node-bluesum! a-node (+ (node-bluesum a-node) b))] |
|||
[else |
|||
;; create the child node if necessary |
|||
(let ([index (rgb->index level r g b)]) |
|||
(unless (vector-ref (node-children a-node) index) |
|||
(let ([new-node (make-node (= level MAX-LEVEL) ; leaf? |
|||
0 ; npixels |
|||
0 ; redsum |
|||
0 ; greensum |
|||
0 ; bluesum |
|||
(make-vector 8 #f) ; no children yet |
|||
#f ; and no next node yet |
|||
#f ; or palette index |
|||
)]) |
|||
(vector-set! (node-children a-node) index new-node) |
|||
(cond |
|||
[(= level MAX-LEVEL) |
|||
;; If we added a leaf, mark it in the octree. |
|||
(set-octree-leaf-count! an-octree |
|||
(add1 (octree-leaf-count an-octree)))] |
|||
[else |
|||
;; Attach the node as a reducible node if it's interior. |
|||
(set-node-next! |
|||
new-node (vector-ref (octree-reduction-heads an-octree) |
|||
(add1 level))) |
|||
(vector-set! (octree-reduction-heads an-octree) |
|||
(add1 level) |
|||
new-node)]))) |
|||
;; and recur on the child node. |
|||
(insert-color-loop (vector-ref (node-children a-node) index) |
|||
(add1 level)))]))) |
|||
;; octree-reduce!: octree -> void |
|||
;; Reduces one of the subtrees, collapsing the children into a single node. |
|||
(define (octree-reduce! an-octree) |
|||
(node-reduce! (pop-reduction-candidate! an-octree) an-octree)) |
|||
;; node-reduce!: node octree -> void |
|||
;; Reduces the interior node. |
|||
(define (node-reduce! a-node an-octree) |
|||
(for ([child (in-vector (node-children a-node))] |
|||
#:when child) |
|||
(set-node-npixels! a-node (+ (node-npixels a-node) |
|||
(node-npixels child))) |
|||
(set-node-redsum! a-node (+ (node-redsum a-node) |
|||
(node-redsum child))) |
|||
(set-node-greensum! a-node (+ (node-greensum a-node) |
|||
(node-greensum child))) |
|||
(set-node-bluesum! a-node (+ (node-bluesum a-node) |
|||
(node-bluesum child))) |
|||
(set-octree-leaf-count! an-octree (sub1 (octree-leaf-count an-octree)))) |
|||
(set-node-leaf?! a-node #t) |
|||
(set-octree-leaf-count! an-octree (add1 (octree-leaf-count an-octree)))) |
|||
;; find-reduction-candidate!: octree -> node |
|||
;; Returns a bottom-level interior node for reduction. Also takes the |
|||
;; candidate out of the conceptual queue of reduction candidates. |
|||
(define (pop-reduction-candidate! an-octree) |
|||
(let loop ([i MAX-LEVEL]) |
|||
(cond |
|||
[(vector-ref (octree-reduction-heads an-octree) i) |
|||
=> |
|||
(lambda (candidate-node) |
|||
(when (> i 0) |
|||
(vector-set! (octree-reduction-heads an-octree) i |
|||
(node-next candidate-node))) |
|||
candidate-node)] |
|||
[else |
|||
(loop (sub1 i))]))) |
|||
;; octree-finalize!: octree -> void |
|||
;; Finalization does a few things: |
|||
;; * Walks through the octree and reduces any interior nodes with just one leaf child. |
|||
;; Optimizes future lookups. |
|||
;; * Fills in the palette of the octree and the palette indexes of the leaf nodes. |
|||
;; * Note: palette index 0 is always reserved for the transparent color. |
|||
(define (octree-finalize! an-octree) |
|||
;; Collapse one-leaf interior nodes. |
|||
(let loop ([a-node (octree-root an-octree)]) |
|||
(for ([child (in-vector (node-children a-node))] |
|||
#:when (and child (not (node-leaf? child)))) |
|||
(loop child) |
|||
(when (interior-node-one-leaf-child? a-node) |
|||
(node-reduce! a-node an-octree)))) |
|||
;; Attach palette entries. |
|||
(let ([current-palette-index 1]) |
|||
(let loop ([a-node (octree-root an-octree)]) |
|||
(cond [(node-leaf? a-node) |
|||
(let ([n (node-npixels a-node)]) |
|||
(vector-set! (octree-palette an-octree) current-palette-index |
|||
(vector (quotient (node-redsum a-node) n) |
|||
(quotient (node-greensum a-node) n) |
|||
(quotient (node-bluesum a-node) n))) |
|||
(set-node-palette-index! a-node current-palette-index) |
|||
(set! current-palette-index (add1 current-palette-index)))] |
|||
[else |
|||
(for ([child (in-vector (node-children a-node))] |
|||
#:when child) |
|||
(loop child))])))) |
|||
;; interior-node-one-leaf-child?: node -> boolean |
|||
(define (interior-node-one-leaf-child? a-node) |
|||
(let ([child-list (filter values (vector->list (node-children a-node)))]) |
|||
(and (= (length child-list) 1) |
|||
(node-leaf? (car child-list))))) |
|||
;; octree-lookup: octree byte byte byte -> (values byte byte byte) |
|||
;; Returns the palettized color. |
|||
(define (octree-lookup an-octree r g b) |
|||
(let* ([index (node-lookup-index (octree-root an-octree) an-octree r g b 0)] |
|||
[vec (vector-ref (octree-palette an-octree) index)]) |
|||
(values (vector-ref vec 0) |
|||
(vector-ref vec 1) |
|||
(vector-ref vec 2)))) |
|||
;; node-lookup-index: node byte byte byte natural-number -> byte |
|||
;; Returns the palettized color index. |
|||
(define (node-lookup-index a-node an-octree r g b level) |
|||
(let loop ([a-node a-node] |
|||
[level level]) |
|||
(if (node-leaf? a-node) |
|||
(node-palette-index a-node) |
|||
(let ([child (vector-ref (node-children a-node) (rgb->index level r g b))]) |
|||
(unless child |
|||
(error 'node-lookup-index |
|||
"color (~a, ~a, ~a) not previously inserted" |
|||
r g b)) |
|||
(loop child (add1 level)))))) |
|||
</lang> |
|||
=={{header|Tcl}}== |
=={{header|Tcl}}== |