package quantize
import (
"image"
"image/color"
"sync"
)
type bucketPool struct {
sync .Pool
maxCap int
m sync .Mutex
}
func (p *bucketPool ) getBucket (c int ) colorBucket {
p .m .Lock ()
if p .maxCap > c {
p .maxCap = p .maxCap * 99 / 100
}
if p .maxCap < c {
p .maxCap = c
}
maxCap := p .maxCap
p .m .Unlock ()
val := p .Pool .Get ()
if val == nil || cap (val .(colorBucket )) < c {
return make (colorBucket , maxCap )[0 :c ]
}
slice := val .(colorBucket )
slice = slice [0 :c ]
for i := range slice {
slice [i ] = colorPriority {}
}
return slice
}
var bpool bucketPool
type AggregationType uint8
const (
Mode AggregationType = iota
Mean
)
type MedianCutQuantizer struct {
Aggregation AggregationType
Weighting func (image .Image , int , int ) uint32
AddTransparent bool
}
func bucketize(colors colorBucket , num int ) (buckets []colorBucket ) {
if len (colors ) == 0 || num == 0 {
return nil
}
bucket := colors
buckets = make ([]colorBucket , 1 , num *2 )
buckets [0 ] = bucket
for len (buckets ) < num && len (buckets ) < len (colors ) {
bucket , buckets = buckets [0 ], buckets [1 :]
if len (bucket ) < 2 {
buckets = append (buckets , bucket )
continue
} else if len (bucket ) == 2 {
buckets = append (buckets , bucket [:1 ], bucket [1 :])
continue
}
left , right := bucket .partition ()
buckets = append (buckets , left , right )
}
return
}
func (q MedianCutQuantizer ) palettize (p color .Palette , buckets []colorBucket ) color .Palette {
for _ , bucket := range buckets {
switch q .Aggregation {
case Mean :
mean := bucket .mean ()
p = append (p , mean )
case Mode :
var best colorPriority
for _ , c := range bucket {
if c .p > best .p {
best = c
}
}
p = append (p , best .RGBA )
}
}
return p
}
func (q MedianCutQuantizer ) quantizeSlice (p color .Palette , colors []colorPriority ) color .Palette {
numColors := cap (p ) - len (p )
addTransparent := q .AddTransparent
if addTransparent {
for _ , c := range p {
if _ , _ , _ , a := c .RGBA (); a == 0 {
addTransparent = false
}
}
if addTransparent {
numColors --
}
}
buckets := bucketize (colors , numColors )
p = q .palettize (p , buckets )
if addTransparent {
p = append (p , color .RGBA {0 , 0 , 0 , 0 })
}
return p
}
func colorAt(m image .Image , x int , y int ) color .RGBA {
switch i := m .(type ) {
case *image .YCbCr :
yi := i .YOffset (x , y )
ci := i .COffset (x , y )
c := color .YCbCr {
i .Y [yi ],
i .Cb [ci ],
i .Cr [ci ],
}
return color .RGBA {c .Y , c .Cb , c .Cr , 255 }
case *image .RGBA :
ci := i .PixOffset (x , y )
return color .RGBA {i .Pix [ci +0 ], i .Pix [ci +1 ], i .Pix [ci +2 ], i .Pix [ci +3 ]}
default :
return color .RGBAModel .Convert (i .At (x , y )).(color .RGBA )
}
}
func (q MedianCutQuantizer ) buildBucket (m image .Image ) (bucket colorBucket ) {
bounds := m .Bounds ()
size := (bounds .Max .X - bounds .Min .X ) * (bounds .Max .Y - bounds .Min .Y ) * 2
sparseBucket := bpool .getBucket (size )
for y := bounds .Min .Y ; y < bounds .Max .Y ; y ++ {
for x := bounds .Min .X ; x < bounds .Max .X ; x ++ {
priority := uint32 (1 )
if q .Weighting != nil {
priority = q .Weighting (m , x , y )
}
if priority != 0 {
c := colorAt (m , x , y )
index := int (c .R )<<16 | int (c .G )<<8 | int (c .B )
for i := 1 ; ; i ++ {
p := &sparseBucket [index %size ]
if p .p == 0 || p .RGBA == c {
*p = colorPriority {p .p + priority , c }
break
}
index += 1 + i
}
}
}
}
bucket = sparseBucket [:0 ]
switch m .(type ) {
case *image .YCbCr :
for _ , p := range sparseBucket {
if p .p != 0 {
r , g , b := color .YCbCrToRGB (p .R , p .G , p .B )
bucket = append (bucket , colorPriority {p .p , color .RGBA {r , g , b , p .A }})
}
}
default :
for _ , p := range sparseBucket {
if p .p != 0 {
bucket = append (bucket , p )
}
}
}
return
}
func (q MedianCutQuantizer ) Quantize (p color .Palette , m image .Image ) color .Palette {
bucket := q .buildBucket (m )
defer bpool .Put (bucket )
return q .quantizeSlice (p , bucket )
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .