matterbridge/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_mediancut.go

210 lines
5.2 KiB
Go
Raw Permalink Normal View History

package libtgsconverter
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
// aggregationType specifies the type of aggregation to be done
type aggregationType uint8
const (
// Mode - pick the highest priority value
mode aggregationType = iota
// Mean - weighted average all values
mean
)
// medianCutQuantizer implements the go draw.Quantizer interface using the Median Cut method
type medianCutQuantizer struct {
// The type of aggregation to be used to find final colors
aggregation aggregationType
// The weighting function to use on each pixel
weighting func(image.Image, int, int) uint32
// Whether need to add a transparent entry after conversion
reserveTransparent bool
}
//bucketize takes a bucket and performs median cut on it to obtain the target number of grouped buckets
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) { // Limit to palette capacity or number of 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
}
// palettize finds a single color to represent a set of color buckets
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
}
// quantizeSlice expands the provided bucket and then palettizes the result
func (q* medianCutQuantizer) quantizeSlice(p color.Palette, colors []colorPriority) color.Palette {
numColors := cap(p) - len(p)
reserveTransparent := q.reserveTransparent
if reserveTransparent {
numColors--
}
buckets := bucketize(colors, numColors)
p = q.palettize(p, buckets)
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)
}
}
// buildBucketMultiple creates a prioritized color slice with all the colors in
// the images.
func (q* medianCutQuantizer) buildBucketMultiple(ms []image.Image) (bucket colorBucket) {
if len(ms) < 1 {
return colorBucket{}
}
bounds := ms[0].Bounds()
size := (bounds.Max.X - bounds.Min.X) * (bounds.Max.Y - bounds.Min.Y) * 2
sparseBucket := bpool.getBucket(size)
for _, m := range ms {
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)
}
c := colorAt(m, x, y)
if c.A == 0 {
if !q.reserveTransparent {
q.reserveTransparent = true
}
continue
}
if priority != 0 {
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 ms[0].(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
}
// Quantize quantizes an image to a palette and returns the palette
func (q* medianCutQuantizer) quantize(p color.Palette, m image.Image) color.Palette {
// Package quantize offers an implementation of the draw.Quantize interface using an optimized Median Cut method,
// including advanced functionality for fine-grained control of color priority
bucket := q.buildBucketMultiple([]image.Image{m})
defer bpool.Put(bucket)
return q.quantizeSlice(p, bucket)
}
// QuantizeMultiple quantizes several images at once to a palette and returns
// the palette
func (q* medianCutQuantizer) quantizeMultiple(p color.Palette, m []image.Image) color.Palette {
bucket := q.buildBucketMultiple(m)
defer bpool.Put(bucket)
return q.quantizeSlice(p, bucket)
}