summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Benau/tgsconverter
diff options
context:
space:
mode:
authorBenau <Benau@users.noreply.github.com>2021-08-25 04:32:50 +0800
committerGitHub <noreply@github.com>2021-08-24 22:32:50 +0200
commit53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 (patch)
tree964a225219099a1a1c282e27913767da588191b4 /vendor/github.com/Benau/tgsconverter
parentd4195deb3a6305c49c50ff30e8af978c7f1bdd92 (diff)
downloadmatterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.gz
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.bz2
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.zip
Convert .tgs with go libraries (and cgo) (telegram) (#1569)
This commit adds support for go/cgo tgs conversion when building with the -tags `cgo` The default binaries are still "pure" go and uses the old way of converting. * Move lottie_convert.py conversion code to its own file * Add optional libtgsconverter * Update vendor * Apply suggestions from code review * Update bridge/helper/libtgsconverter.go Co-authored-by: Wim <wim@42.be>
Diffstat (limited to 'vendor/github.com/Benau/tgsconverter')
-rw-r--r--vendor/github.com/Benau/tgsconverter/LICENSE24
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/apng.go51
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/gif.go81
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/imagewriter.go40
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/lib.go160
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/png.go30
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_bucket.go119
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_mediancut.go209
-rw-r--r--vendor/github.com/Benau/tgsconverter/libtgsconverter/webp.go39
9 files changed, 753 insertions, 0 deletions
diff --git a/vendor/github.com/Benau/tgsconverter/LICENSE b/vendor/github.com/Benau/tgsconverter/LICENSE
new file mode 100644
index 00000000..86fd0417
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/LICENSE
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2021, (see AUTHORS)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/apng.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/apng.go
new file mode 100644
index 00000000..78541533
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/apng.go
@@ -0,0 +1,51 @@
+package libtgsconverter
+
+import "bytes"
+import "image"
+
+import "github.com/kettek/apng"
+import "github.com/av-elier/go-decimal-to-rational"
+
+type toapng struct {
+ apng apng.APNG
+ prev_frame *image.RGBA
+}
+
+func(to_apng *toapng) init(w uint, h uint, options ConverterOptions) {
+}
+
+func(to_apng *toapng) SupportsAnimation() bool {
+ return true
+}
+
+func (to_apng *toapng) AddFrame(image *image.RGBA, fps uint) error {
+ if to_apng.prev_frame != nil && sameImage(to_apng.prev_frame, image) {
+ var idx = len(to_apng.apng.Frames) - 1
+ var prev_fps = float64(to_apng.apng.Frames[idx].DelayNumerator) / float64(to_apng.apng.Frames[idx].DelayDenominator)
+ prev_fps += 1.0 / float64(fps)
+ rat := dectofrac.NewRatP(prev_fps, 0.001)
+ to_apng.apng.Frames[idx].DelayNumerator = uint16(rat.Num().Int64())
+ to_apng.apng.Frames[idx].DelayDenominator = uint16(rat.Denom().Int64())
+ return nil
+ }
+ f := apng.Frame{}
+ f.Image = image
+ f.DelayNumerator = 1
+ f.DelayDenominator = uint16(fps)
+ f.DisposeOp = apng.DISPOSE_OP_BACKGROUND
+ f.BlendOp = apng.BLEND_OP_SOURCE
+ f.IsDefault = false
+ to_apng.apng.Frames = append(to_apng.apng.Frames, f)
+ to_apng.prev_frame = image
+ return nil
+}
+
+func (to_apng *toapng) Result() []byte {
+ var data []byte
+ w := bytes.NewBuffer(data)
+ err := apng.Encode(w, to_apng.apng)
+ if err != nil {
+ return nil
+ }
+ return w.Bytes()
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/gif.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/gif.go
new file mode 100644
index 00000000..c4f5c5d9
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/gif.go
@@ -0,0 +1,81 @@
+package libtgsconverter
+
+import "bytes"
+
+import "image"
+import "image/color"
+import "image/gif"
+
+type togif struct {
+ gif gif.GIF
+ images []image.Image
+ prev_frame *image.RGBA
+}
+
+func(to_gif *togif) init(w uint, h uint, options ConverterOptions) {
+ to_gif.gif.Config.Width = int(w)
+ to_gif.gif.Config.Height = int(h)
+}
+
+func(to_gif *togif) SupportsAnimation() bool {
+ return true
+}
+
+func (to_gif *togif) AddFrame(image *image.RGBA, fps uint) error {
+ var fps_int = int(1.0 / float32(fps) * 100.)
+ if to_gif.prev_frame != nil && sameImage(to_gif.prev_frame, image) {
+ to_gif.gif.Delay[len(to_gif.gif.Delay) - 1] += fps_int
+ return nil
+ }
+ to_gif.gif.Image = append(to_gif.gif.Image, nil)
+ to_gif.gif.Delay = append(to_gif.gif.Delay, fps_int)
+ to_gif.gif.Disposal = append(to_gif.gif.Disposal, gif.DisposalBackground)
+ to_gif.images = append(to_gif.images, image)
+ to_gif.prev_frame = image
+ return nil
+}
+
+func (to_gif *togif) Result() []byte {
+ q := medianCutQuantizer{mode, nil, false}
+ p := q.quantizeMultiple(make([]color.Color, 0, 256), to_gif.images)
+ // Add transparent entry finally
+ var trans_idx uint8 = 0
+ if q.reserveTransparent {
+ trans_idx = uint8(len(p))
+ }
+ var id_map = make(map[uint32]uint8)
+ for i, img := range to_gif.images {
+ pi := image.NewPaletted(img.Bounds(), p)
+ for y := 0; y < img.Bounds().Dy(); y++ {
+ for x := 0; x < img.Bounds().Dx(); x++ {
+ c := img.At(x, y)
+ cr, cg, cb, ca := c.RGBA()
+ cid := (cr >> 8) << 16 | cg | (cb >> 8)
+ if q.reserveTransparent && ca == 0 {
+ pi.Pix[pi.PixOffset(x, y)] = trans_idx
+ } else if val, ok := id_map[cid]; ok {
+ pi.Pix[pi.PixOffset(x, y)] = val
+ } else {
+ val := uint8(p.Index(c))
+ pi.Pix[pi.PixOffset(x, y)] = val
+ id_map[cid] = val
+ }
+ }
+ }
+ to_gif.gif.Image[i] = pi
+ }
+ if q.reserveTransparent {
+ p = append(p, color.RGBA{0, 0, 0, 0})
+ }
+ for _, img := range to_gif.gif.Image {
+ img.Palette = p
+ }
+ to_gif.gif.Config.ColorModel = p
+ var data []byte
+ w := bytes.NewBuffer(data)
+ err := gif.EncodeAll(w, &to_gif.gif)
+ if err != nil {
+ return nil
+ }
+ return w.Bytes()
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/imagewriter.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/imagewriter.go
new file mode 100644
index 00000000..9549e337
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/imagewriter.go
@@ -0,0 +1,40 @@
+package libtgsconverter
+
+import "image"
+
+type imageWriter interface {
+ init(w uint, h uint, options ConverterOptions)
+ SupportsAnimation() bool
+ AddFrame(image *image.RGBA, fps uint) error
+ Result() []byte
+}
+
+func sameImage(a *image.RGBA, b *image.RGBA) bool {
+ if len(a.Pix) != len(b.Pix) {
+ return false
+ }
+ for i, v := range a.Pix {
+ if v != b.Pix[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func newImageWriter(extension string, w uint, h uint, options ConverterOptions) imageWriter {
+ var writer imageWriter
+ switch extension {
+ case "apng":
+ writer = &toapng{}
+ case "gif":
+ writer = &togif{}
+ case "png":
+ writer = &topng{}
+ case "webp":
+ writer = &towebp{}
+ default:
+ return nil
+ }
+ writer.init(w, h, options)
+ return writer
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/lib.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/lib.go
new file mode 100644
index 00000000..af6f8ab0
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/lib.go
@@ -0,0 +1,160 @@
+package libtgsconverter
+
+import "bytes"
+import "errors"
+import "compress/gzip"
+import "image"
+import "io/ioutil"
+
+import "github.com/Benau/go_rlottie"
+
+type ConverterOptions interface {
+ SetExtension(ext string)
+ SetFPS(fps uint)
+ SetScale(scale float32)
+ SetWebpQuality(webp_quality float32)
+ GetExtension() string
+ GetFPS() uint
+ GetScale() float32
+ GetWebpQuality() float32
+}
+
+type converter_options struct {
+ // apng, gif, png or webp
+ extension string
+ // Frame per second of output image (if you specify apng, gif or webp)
+ fps uint
+ // Scale of image result
+ scale float32
+ // Webp encoder quality (0 to 100)
+ webpQuality float32
+}
+
+func(opt *converter_options) SetExtension(ext string) {
+ opt.extension = ext
+}
+
+func(opt *converter_options) SetFPS(fps uint) {
+ opt.fps = fps
+}
+
+func(opt *converter_options) SetScale(scale float32) {
+ opt.scale = scale
+}
+
+func(opt *converter_options) SetWebpQuality(webp_quality float32) {
+ opt.webpQuality = webp_quality
+}
+
+func(opt *converter_options) GetExtension() string {
+ return opt.extension
+}
+
+func(opt *converter_options) GetFPS() uint {
+ return opt.fps
+}
+
+func(opt *converter_options) GetScale() float32 {
+ return opt.scale
+}
+
+func(opt *converter_options) GetWebpQuality() float32 {
+ return opt.webpQuality
+}
+
+func NewConverterOptions() ConverterOptions {
+ return &converter_options{"png", 30, 1.0, 75}
+}
+
+func imageFromBuffer(p []byte, w uint, h uint) *image.RGBA {
+ // rlottie use ARGB32_Premultiplied
+ for i := 0; i < len(p); i += 4 {
+ p[i + 0], p[i + 2] = p[i + 2], p[i + 0]
+ }
+ m := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))
+ m.Pix = p
+ m.Stride = int(w) * 4
+ return m
+}
+
+var disabled_cache = false
+func ImportFromData(data []byte, options ConverterOptions) ([]byte, error) {
+ if !disabled_cache {
+ disabled_cache = true
+ go_rlottie.LottieConfigureModelCacheSize(0)
+ }
+ z, err := gzip.NewReader(bytes.NewReader(data))
+ if err != nil {
+ return nil, errors.New("Failed to create gzip reader:" + err.Error())
+ }
+ uncompressed, err := ioutil.ReadAll(z)
+ if err != nil {
+ return nil, errors.New("Failed to read gzip archive")
+ }
+ z.Close()
+
+ animation := go_rlottie.LottieAnimationFromData(string(uncompressed[:]), "", "")
+ if animation == nil {
+ return nil, errors.New("Failed to import lottie animation data")
+ }
+
+ w, h := go_rlottie.LottieAnimationGetSize(animation)
+ w = uint(float32(w) * options.GetScale())
+ h = uint(float32(h) * options.GetScale())
+
+ frame_rate := go_rlottie.LottieAnimationGetFramerate(animation)
+ frame_count := go_rlottie.LottieAnimationGetTotalframe(animation)
+ duration := float32(frame_count) / float32(frame_rate)
+ var desired_framerate = float32(options.GetFPS())
+ // Most (Gif) player doesn't support ~60fps (found in most tgs)
+ if desired_framerate > 50. {
+ desired_framerate = 50.
+ }
+ step := 1.0 / desired_framerate
+
+ writer := newImageWriter(options.GetExtension(), w, h, options)
+ if writer == nil {
+ return nil, errors.New("Failed create imagewriter")
+ }
+
+ var i float32
+ for i = 0.; i < duration; i += step {
+ frame := go_rlottie.LottieAnimationGetFrameAtPos(animation, i / duration)
+ buf := make([]byte, w * h * 4)
+ go_rlottie.LottieAnimationRender(animation, frame, buf, w, h, w * 4)
+ m := imageFromBuffer(buf, w, h)
+ err := writer.AddFrame(m, uint(desired_framerate))
+ if err != nil {
+ return nil, errors.New("Failed to add frame:" + err.Error())
+ }
+ if !writer.SupportsAnimation() {
+ break
+ }
+ }
+ go_rlottie.LottieAnimationDestroy(animation)
+ return writer.Result(), nil
+}
+
+func ImportFromFile(path string, options ConverterOptions) ([]byte, error) {
+ tgs, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, errors.New("Error when opening file:" + err.Error())
+ }
+ return ImportFromData(tgs, options)
+}
+
+func SupportsExtension(extension string) (bool) {
+ switch extension {
+ case "apng":
+ fallthrough
+ case "gif":
+ fallthrough
+ case "png":
+ fallthrough
+ case "webp":
+ return true
+ default:
+ return false
+ }
+ return false
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/png.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/png.go
new file mode 100644
index 00000000..cf492ea4
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/png.go
@@ -0,0 +1,30 @@
+package libtgsconverter
+
+import "bytes"
+import "image"
+import "image/png"
+
+type topng struct {
+ result []byte
+}
+
+func(to_png *topng) init(w uint, h uint, options ConverterOptions) {
+}
+
+func(to_png *topng) SupportsAnimation() bool {
+ return false
+}
+
+func (to_png *topng) AddFrame(image *image.RGBA, fps uint) error {
+ var data []byte
+ w := bytes.NewBuffer(data)
+ if err := png.Encode(w, image); err != nil {
+ return err
+ }
+ to_png.result = w.Bytes()
+ return nil
+}
+
+func (to_png *topng) Result() []byte {
+ return to_png.result
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_bucket.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_bucket.go
new file mode 100644
index 00000000..1f00c685
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_bucket.go
@@ -0,0 +1,119 @@
+package libtgsconverter
+
+import "image/color"
+
+type colorAxis uint8
+
+// Color axis constants
+const (
+ red colorAxis = iota
+ green
+ blue
+)
+
+type colorPriority struct {
+ p uint32
+ color.RGBA
+}
+
+func (c colorPriority) axis(span colorAxis) uint8 {
+ switch span {
+ case red:
+ return c.R
+ case green:
+ return c.G
+ default:
+ return c.B
+ }
+}
+
+type colorBucket []colorPriority
+
+func (cb colorBucket) partition() (colorBucket, colorBucket) {
+ mean, span := cb.span()
+ left, right := 0, len(cb)-1
+ for left < right {
+ cb[left], cb[right] = cb[right], cb[left]
+ for cb[left].axis(span) < mean && left < right {
+ left++
+ }
+ for cb[right].axis(span) >= mean && left < right {
+ right--
+ }
+ }
+ if left == 0 {
+ return cb[:1], cb[1:]
+ }
+ if left == len(cb)-1 {
+ return cb[:len(cb)-1], cb[len(cb)-1:]
+ }
+ return cb[:left], cb[left:]
+}
+
+func (cb colorBucket) mean() color.RGBA {
+ var r, g, b uint64
+ var p uint64
+ for _, c := range cb {
+ p += uint64(c.p)
+ r += uint64(c.R) * uint64(c.p)
+ g += uint64(c.G) * uint64(c.p)
+ b += uint64(c.B) * uint64(c.p)
+ }
+ return color.RGBA{uint8(r / p), uint8(g / p), uint8(b / p), 255}
+}
+
+type constraint struct {
+ min uint8
+ max uint8
+ vals [256]uint64
+}
+
+func (c *constraint) update(index uint8, p uint32) {
+ if index < c.min {
+ c.min = index
+ }
+ if index > c.max {
+ c.max = index
+ }
+ c.vals[index] += uint64(p)
+}
+
+func (c *constraint) span() uint8 {
+ return c.max - c.min
+}
+
+func (cb colorBucket) span() (uint8, colorAxis) {
+ var R, G, B constraint
+ R.min = 255
+ G.min = 255
+ B.min = 255
+ var p uint64
+ for _, c := range cb {
+ R.update(c.R, c.p)
+ G.update(c.G, c.p)
+ B.update(c.B, c.p)
+ p += uint64(c.p)
+ }
+ var toCount *constraint
+ var span colorAxis
+ if R.span() > G.span() && R.span() > B.span() {
+ span = red
+ toCount = &R
+ } else if G.span() > B.span() {
+ span = green
+ toCount = &G
+ } else {
+ span = blue
+ toCount = &B
+ }
+ var counted uint64
+ var i int
+ var c uint64
+ for i, c = range toCount.vals {
+ if counted > p/2 || counted+c == p {
+ break
+ }
+ counted += c
+ }
+ return uint8(i), span
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_mediancut.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_mediancut.go
new file mode 100644
index 00000000..850708b9
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/quantize_mediancut.go
@@ -0,0 +1,209 @@
+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)
+}
diff --git a/vendor/github.com/Benau/tgsconverter/libtgsconverter/webp.go b/vendor/github.com/Benau/tgsconverter/libtgsconverter/webp.go
new file mode 100644
index 00000000..60e9887c
--- /dev/null
+++ b/vendor/github.com/Benau/tgsconverter/libtgsconverter/webp.go
@@ -0,0 +1,39 @@
+package libtgsconverter
+
+import "bytes"
+import "image"
+
+import "github.com/sizeofint/webpanimation"
+
+type towebp struct {
+ timestamp int
+ webpanim *webpanimation.WebpAnimation
+ config webpanimation.WebPConfig
+}
+
+func(to_webp *towebp) init(w uint, h uint, options ConverterOptions) {
+ to_webp.timestamp = 0
+ to_webp.webpanim = webpanimation.NewWebpAnimation(int(w), int(h), 0)
+ to_webp.config = webpanimation.NewWebpConfig()
+ to_webp.config.SetQuality(options.GetWebpQuality())
+}
+
+func(to_webp *towebp) SupportsAnimation() bool {
+ return true
+}
+
+func (to_webp *towebp) AddFrame(image *image.RGBA, fps uint) error {
+ err := to_webp.webpanim.AddFrame(image, to_webp.timestamp, to_webp.config)
+ to_webp.timestamp += int((1.0 / float32(fps)) * 1000.)
+ return err
+}
+
+func (to_webp *towebp) Result() []byte {
+ var buf bytes.Buffer
+ err := to_webp.webpanim.Encode(&buf)
+ if err != nil {
+ return nil
+ }
+ to_webp.webpanim.ReleaseMemory()
+ return buf.Bytes()
+}