diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ec2d8e2f..06e2ed76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,12 +38,14 @@ jobs:
with:
go-version: '1.20'
+ - name: Install Dependencies
+ run: |
+ sudo apt-get install -y libwebp-dev
+ go install honnef.co/go/tools/cmd/staticcheck@latest
+
- name: build
run: go build cmd/*go
- - name: Install dependencies
- run: go install honnef.co/go/tools/cmd/staticcheck@latest
-
- name: staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
diff --git a/Dockerfile b/Dockerfile
index d1cd14f9..c05125f0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,8 @@ WORKDIR /app/ui
COPY ui ./
RUN npm install && npm run build
-FROM golang:1.20 AS builder
+# switch to bullseye due to https://github.com/GoogleContainerTools/distroless/issues/1342
+FROM golang:1.20-bullseye AS builder
WORKDIR /app
@@ -20,7 +21,9 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Minify Assets
# hadolint ignore=DL3008
RUN apt-get update \
- && apt-get install -y --no-install-recommends minify \
+ && apt-get install -y --no-install-recommends \
+ minify \
+ libwebp-dev \
&& find ./public -type f \( \
-name "*.html" \
-o -name '*.js' \
@@ -29,10 +32,14 @@ RUN apt-get update \
-print0 | \
xargs -0 -I '{}' sh -c 'minify -o "{}" "{}"'
-RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o ./bin/song-stitch cmd/*.go
+RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o ./bin/song-stitch cmd/*.go
# hadolint ignore=DL3006
FROM gcr.io/distroless/base-debian11 AS build-release-stage
+ARG ARCH=aarch64
+
+# Copy dependency for webp
+COPY --from=builder /usr/lib/${ARCH}-linux-gnu/libwebp.so* /usr/lib/${ARCH}-linux-gnu/
WORKDIR /app
diff --git a/README.md b/README.md
index 6ba52429..3077b141 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,9 @@ If you would like to run SongStitch yourself, below are the instructions on how
### Requirements
-There are currently 2 options to run SongStitch yourself.
+There are currently two options to run SongStitch yourself.
-1. Build and run the application locally. This requires you to have `go` and `npm` installed.
+1. Build and run the application locally. This requires you to have the `go` and `npm` installed, and the [WebP](https://developers.google.com/speed/webp/) library.
2. Run the application inside the docker container. This requires `docker` to be installed.
diff --git a/go.mod b/go.mod
index 95f64ff8..50df53ef 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/ggicci/httpin v0.10.1
github.com/go-playground/validator/v10 v10.14.0
github.com/justinas/alice v1.2.0
+ github.com/kolesa-team/go-webp v1.0.4
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/rs/zerolog v1.29.1
)
diff --git a/go.sum b/go.sum
index e5cfb8cd..918f9cfd 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
+github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
+github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
@@ -57,6 +59,7 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
@@ -66,6 +69,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
diff --git a/internal/api/api.go b/internal/api/api.go
index 1cab0d2a..a07ba2c9 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -1,8 +1,10 @@
package api
import (
+ "bytes"
"context"
"errors"
+ "fmt"
"image"
"image/jpeg"
"net/http"
@@ -25,15 +27,15 @@ type CollageRequest struct {
DisplayAlbum bool `in:"query=album;default=false"`
DisplayTrack bool `in:"query=track;default=false"`
PlayCount bool `in:"query=playcount;default=false"`
- Compress bool `in:"query=compress;default=false"`
Width uint `in:"query=width;default=0" validate:"gte=0,lte=3000"`
Height uint `in:"query=height;default=0" validate:"gte=0,lte=3000"`
Method string `in:"query=method;default=album" validate:"required,oneof=album artist track"`
FontSize int `in:"query=fontsize;default=12" validate:"gte=8,lte=30"`
BoldFont bool `in:"query=boldfont;default=false"`
+ Webp bool `in:"query=webp;default=false"`
}
-func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image, error) {
+func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image, *bytes.Buffer, error) {
count := request.Rows * request.Columns
imageSize := "extralarge"
imageDimension := 300
@@ -56,10 +58,10 @@ func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image
Resize: request.Width > 0 || request.Height > 0,
Width: request.Width,
Height: request.Height,
- Compress: request.Compress,
ImageDimension: imageDimension,
FontSize: float64(request.FontSize),
BoldFont: request.BoldFont,
+ Webp: request.Webp,
Rows: request.Rows,
Columns: request.Columns,
}
@@ -74,7 +76,7 @@ func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image
case constants.TRACK:
return collages.GenerateCollageForTrack(ctx, request.Username, period, count, imageSize, displayOptions)
default:
- return nil, errors.New("invalid collage type")
+ return nil, nil, errors.New("invalid collage type")
}
}
@@ -103,15 +105,15 @@ func Collage(w http.ResponseWriter, r *http.Request) {
Bool("album", request.DisplayAlbum).
Bool("track", request.DisplayTrack).
Bool("playcount", request.PlayCount).
- Bool("compress", request.Compress).
Uint("width", request.Width).
Uint("height", request.Height).
Str("method", request.Method).
Int("fontsize", request.FontSize).
Bool("boldfont", request.BoldFont).
+ Bool("webp", request.Webp).
Msg("Generating collage")
- image, err := generateCollage(ctx, request)
+ image, buffer, err := generateCollage(ctx, request)
if ctx.Err() != nil {
logger.Warn().Err(ctx.Err()).Msg("Context cancelled")
// 499 is the http status code for client closed request
@@ -133,17 +135,24 @@ func Collage(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "image/jpeg")
- err = jpeg.Encode(w, *image, nil)
if ctx.Err() != nil {
logger.Warn().Err(ctx.Err()).Msg("Context cancelled")
// 499 is the http status code for client closed request
http.Error(w, "Context cancelled", 499)
return
}
- if err != nil {
- logger.Error().Err(err).Msg("Error occurred encoding collage")
- http.Error(w, "An error occurred processing your request", http.StatusInternalServerError)
- return
+
+ if request.Webp {
+ w.Header().Set("Content-Type", "image/webp")
+ w.Write(buffer.Bytes())
+ fmt.Println("Setting webp")
+ } else {
+ w.Header().Set("Content-Type", "image/jpeg")
+ err = jpeg.Encode(w, *image, nil)
+ if err != nil {
+ logger.Error().Err(err).Msg("Error occurred encoding collage")
+ http.Error(w, "An error occurred processing your request", http.StatusInternalServerError)
+ return
+ }
}
}
diff --git a/internal/collages/album.go b/internal/collages/album.go
index b73e2a5e..b6f6225b 100644
--- a/internal/collages/album.go
+++ b/internal/collages/album.go
@@ -1,6 +1,7 @@
package collages
import (
+ "bytes"
"context"
"errors"
"image"
@@ -57,10 +58,13 @@ func (a *LastFMTopAlbums) GetTotalFetched() int {
return len(a.TopAlbums.Albums)
}
-func GenerateCollageForAlbum(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
+func GenerateCollageForAlbum(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
+ if count > 225 {
+ return nil, nil, constants.ErrTooManyImages
+ }
albums, err := getAlbums(ctx, username, period, count, imageSize)
if err != nil {
- return nil, err
+ return nil, nil, err
}
generator.DownloadImages(ctx, albums)
diff --git a/internal/collages/artist.go b/internal/collages/artist.go
index f6ada0c8..5a9666b1 100644
--- a/internal/collages/artist.go
+++ b/internal/collages/artist.go
@@ -1,6 +1,7 @@
package collages
import (
+ "bytes"
"context"
"errors"
"image"
@@ -51,13 +52,13 @@ func (a *LastFMTopArtists) GetTotalFetched() int {
return len(a.TopArtists.Artists)
}
-func GenerateCollageForArtist(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
+func GenerateCollageForArtist(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
if count > 100 {
- return nil, constants.ErrTooManyImages
+ return nil, nil, constants.ErrTooManyImages
}
artists, err := getArtists(ctx, username, period, count, imageSize)
if err != nil {
- return nil, err
+ return nil, nil, err
}
generator.DownloadImages(ctx, artists)
diff --git a/internal/collages/track.go b/internal/collages/track.go
index 1636042b..d86a9a17 100644
--- a/internal/collages/track.go
+++ b/internal/collages/track.go
@@ -1,6 +1,7 @@
package collages
import (
+ "bytes"
"context"
"errors"
"image"
@@ -59,13 +60,13 @@ func (t *LastFMTopTracks) GetTotalFetched() int {
return len(t.TopTracks.Tracks)
}
-func GenerateCollageForTrack(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
+func GenerateCollageForTrack(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
if count > 25 {
- return nil, constants.ErrTooManyImages
+ return nil, nil, constants.ErrTooManyImages
}
tracks, err := getTracks(ctx, username, period, count, imageSize)
if err != nil {
- return nil, err
+ return nil, nil, err
}
generator.DownloadImages(ctx, tracks)
diff --git a/internal/generator/image.go b/internal/generator/image.go
index f2449267..9fbcedf1 100644
--- a/internal/generator/image.go
+++ b/internal/generator/image.go
@@ -5,13 +5,15 @@ import (
"context"
"fmt"
"image"
- "image/jpeg"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/fogleman/gg"
+ "github.com/kolesa-team/go-webp/encoder"
+ "github.com/kolesa-team/go-webp/webp"
+
"github.com/nfnt/resize"
"github.com/rs/zerolog"
)
@@ -30,6 +32,7 @@ type DisplayOptions struct {
Rows int
Columns int
ImageDimension int
+ Webp bool
}
const (
@@ -105,16 +108,17 @@ func resizeImage(ctx context.Context, img *image.Image, width uint, height uint)
return &result
}
-func compressImage(collage *image.Image, quality int) (image.Image, error) {
- var buf bytes.Buffer
- err := jpeg.Encode(&buf, *collage, &jpeg.Options{Quality: quality})
+func webpEncode(buf *bytes.Buffer, collage *image.Image, quality float32) error {
+ options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, quality)
if err != nil {
- return nil, err
+ return err
}
- return jpeg.Decode(bytes.NewReader(buf.Bytes()))
+
+ err = webp.Encode(buf, *collage, options)
+ return err
}
-func CreateCollage[T Drawable](ctx context.Context, collageElements []T, displayOptions DisplayOptions) (*image.Image, error) {
+func CreateCollage[T Drawable](ctx context.Context, collageElements []T, displayOptions DisplayOptions) (*image.Image, *bytes.Buffer, error) {
start := time.Now()
logger := zerolog.Ctx(ctx)
@@ -144,15 +148,15 @@ func CreateCollage[T Drawable](ctx context.Context, collageElements []T, display
collage = *resizeImage(ctx, &collage, displayOptions.Width, displayOptions.Height)
}
- if displayOptions.Compress {
- collageCompressed, err := compressImage(&collage, compressionQuality)
+ collageBuffer := new(bytes.Buffer)
+
+ if displayOptions.Webp {
+ err := webpEncode(collageBuffer, &collage, compressionQuality)
if err != nil {
- // Skip and just serve the non-compressed image
- logger.Err(err).Msg("Unable to compress image")
- } else {
- collage = collageCompressed
+ logger.Err(err).Msg("Unable to create Webp image")
}
}
+
logger.Info().Dur("duration", time.Since(start)).Int("rows", displayOptions.Rows).Int("columns", displayOptions.Columns).Msg("Collage created")
- return &collage, nil
+ return &collage, collageBuffer, nil
}
diff --git a/ui/src/lib/Form.svelte b/ui/src/lib/Form.svelte
index c8c8669e..f9b185b2 100644
--- a/ui/src/lib/Form.svelte
+++ b/ui/src/lib/Form.svelte
@@ -37,7 +37,7 @@
.min(1),
advancedOptions: z.boolean().optional(),
showTextSize: z.boolean().optional(),
- lossyCompression: z.boolean().optional(),
+ WebPLossyCompression: z.boolean().optional(),
showBoldtext: z.boolean().optional(),
textSize: z.string().optional(),
});
@@ -66,8 +66,8 @@
if (values.showTextSize) {
params.append('fontsize', values.textSize);
}
- if (values.lossyCompression) {
- params.append('compress', values.lossyCompression.toString());
+ if (values.WebPLossyCompression) {
+ params.append('webp', values.WebPLossyCompression.toString());
}
if (values.showBoldtext) {
params.append('boldfont', values.showBoldtext.toString());
@@ -99,7 +99,7 @@
showTextSize: false,
textSize: '12',
showBoldtext: false,
- lossyCompression: false,
+ WebPLossyCompression: false,
},
});
@@ -237,10 +237,10 @@
{/if}
diff --git a/vendor/github.com/kolesa-team/go-webp/AUTHORS b/vendor/github.com/kolesa-team/go-webp/AUTHORS
new file mode 100644
index 00000000..ffd413b9
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/AUTHORS
@@ -0,0 +1,2 @@
+Google LLC (https://opensource.google.com/)
+Amangeldy Kadyl
diff --git a/vendor/github.com/kolesa-team/go-webp/LICENSE b/vendor/github.com/kolesa-team/go-webp/LICENSE
new file mode 100644
index 00000000..1e30dfde
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 Amangeldy Kadyl
+
+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/kolesa-team/go-webp/decoder/decoder.go b/vendor/github.com/kolesa-team/go-webp/decoder/decoder.go
new file mode 100644
index 00000000..68545607
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/decoder/decoder.go
@@ -0,0 +1,137 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package decoder
+
+/*
+#cgo linux LDFLAGS: -lwebp
+#cgo darwin pkg-config: libwebp
+#include
+#include
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+ "image"
+ "io"
+ "unsafe"
+
+ "github.com/kolesa-team/go-webp/utils"
+)
+
+// Decoder stores information to decode picture
+type Decoder struct {
+ data []byte
+ options *Options
+ config *C.WebPDecoderConfig
+ dPtr *C.uint8_t
+ sPtr C.size_t
+}
+
+// NewDecoder return new decoder instance
+func NewDecoder(r io.Reader, options *Options) (d *Decoder, err error) {
+ var data []byte
+
+ if data, err = io.ReadAll(r); err != nil {
+ return nil, err
+ }
+
+ if len(data) == 0 {
+ return nil, errors.New("data is empty")
+ }
+
+ if options == nil {
+ options = &Options{}
+ }
+ d = &Decoder{data: data, options: options}
+
+ if d.config, err = d.options.GetConfig(); err != nil {
+ return nil, err
+ }
+
+ d.dPtr = (*C.uint8_t)(&d.data[0])
+ d.sPtr = (C.size_t)(len(d.data))
+
+ // получаем WebPBitstreamFeatures
+ if status := d.parseFeatures(d.dPtr, d.sPtr); status != utils.Vp8StatusOk {
+ return nil, errors.New(fmt.Sprintf("cannot fetch features: %s", status.String()))
+ }
+
+ return
+}
+
+// Decode picture from reader
+func (d *Decoder) Decode() (image.Image, error) {
+ // вписываем размеры итоговой картинки
+ d.config.output.width, d.config.output.height = d.getOutputDimensions()
+ // указываем что декодируем в RGBA
+ d.config.output.colorspace = C.MODE_RGBA
+ d.config.output.is_external_memory = 1
+
+ img := image.NewNRGBA(image.Rectangle{Max: image.Point{
+ X: int(d.config.output.width),
+ Y: int(d.config.output.height),
+ }})
+
+ buff := (*C.WebPRGBABuffer)(unsafe.Pointer(&d.config.output.u[0]))
+ buff.stride = C.int(img.Stride)
+ buff.rgba = (*C.uint8_t)(&img.Pix[0])
+ buff.size = (C.size_t)(len(img.Pix))
+
+ if status := utils.VP8StatusCode(C.WebPDecode(d.dPtr, d.sPtr, d.config)); status != utils.Vp8StatusOk {
+ return nil, errors.New(fmt.Sprintf("cannot decode picture: %s", status.String()))
+ }
+
+ return img, nil
+}
+
+// GetFeatures return information about picture: width, height ...
+func (d *Decoder) GetFeatures() utils.BitstreamFeatures {
+ return utils.BitstreamFeatures{
+ Width: int(d.config.input.width),
+ Height: int(d.config.input.height),
+ HasAlpha: int(d.config.input.has_alpha) == 1,
+ HasAnimation: int(d.config.input.has_animation) == 1,
+ Format: utils.FormatType(d.config.input.format),
+ }
+}
+
+// parse features from picture
+func (d *Decoder) parseFeatures(dataPtr *C.uint8_t, sizePtr C.size_t) utils.VP8StatusCode {
+ return utils.VP8StatusCode(C.WebPGetFeatures(dataPtr, sizePtr, &d.config.input))
+}
+
+// return dimensions of result image
+func (d *Decoder) getOutputDimensions() (width, height C.int) {
+ width = d.config.input.width
+ height = d.config.input.height
+
+ if d.config.options.use_scaling > 0 {
+ width = d.config.options.scaled_width
+ height = d.config.options.scaled_height
+ } else if d.config.options.use_cropping > 0 {
+ width = d.config.options.crop_width
+ height = d.config.options.crop_height
+ }
+
+ return
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/decoder/options.go b/vendor/github.com/kolesa-team/go-webp/decoder/options.go
new file mode 100644
index 00000000..311a7253
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/decoder/options.go
@@ -0,0 +1,91 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package decoder
+
+/*
+#cgo LDFLAGS: -lwebp
+#include
+*/
+import "C"
+import (
+ "errors"
+ "image"
+)
+
+// Options specifies webp decoding parameters
+type Options struct {
+ BypassFiltering bool
+ NoFancyUpsampling bool
+ Crop image.Rectangle
+ Scale image.Rectangle
+ UseThreads bool
+ Flip bool
+ DitheringStrength int
+ AlphaDitheringStrength int
+}
+
+// GetConfig build WebPDecoderConfig for libwebp
+func (o *Options) GetConfig() (*C.WebPDecoderConfig, error) {
+ config := C.WebPDecoderConfig{}
+
+ if C.WebPInitDecoderConfig(&config) == 0 {
+ return nil, errors.New("cannot init decoder config")
+ }
+
+ if o.BypassFiltering {
+ config.options.bypass_filtering = 1
+ }
+
+ if o.NoFancyUpsampling {
+ config.options.no_fancy_upsampling = 1
+ }
+
+ // проверяем надо ли кропнуть
+ if o.Crop.Max.X > 0 && o.Crop.Max.Y > 0 {
+ config.options.use_cropping = 1
+ config.options.crop_left = C.int(o.Crop.Min.X)
+ config.options.crop_top = C.int(o.Crop.Min.Y)
+ config.options.crop_width = C.int(o.Crop.Max.X)
+ config.options.crop_height = C.int(o.Crop.Max.Y)
+ }
+
+ // проверяем надо ли заскейлить
+ if o.Scale.Max.X > 0 && o.Scale.Max.Y > 0 {
+ config.options.use_scaling = 1
+ config.options.scaled_width = C.int(o.Scale.Max.X)
+ config.options.scaled_height = C.int(o.Scale.Max.Y)
+ }
+
+ if o.UseThreads {
+ config.options.use_threads = 1
+ }
+
+ config.options.dithering_strength = C.int(o.DitheringStrength)
+
+ if o.Flip {
+ config.options.flip = 1
+ }
+
+ config.options.alpha_dithering_strength = C.int(o.AlphaDitheringStrength)
+
+ return &config, nil
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/encoder/encoder.go b/vendor/github.com/kolesa-team/go-webp/encoder/encoder.go
new file mode 100644
index 00000000..9bb96ddf
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/encoder/encoder.go
@@ -0,0 +1,127 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package encoder
+
+/*
+
+#cgo linux LDFLAGS: -lwebp
+#cgo darwin pkg-config: libwebp
+#include
+#include
+static uint8_t* encodeNRBBA(WebPConfig* config, const uint8_t* rgba, int width, int height, int stride, size_t* output_size) {
+ WebPPicture pic;
+ WebPMemoryWriter wrt;
+ int ok;
+
+ if (!WebPPictureInit(&pic)) {
+ return NULL;
+ }
+
+ pic.use_argb = 1;
+ pic.width = width;
+ pic.height = height;
+ pic.writer = WebPMemoryWrite;
+ pic.custom_ptr = &wrt;
+ WebPMemoryWriterInit(&wrt);
+
+ ok = WebPPictureImportRGBA(&pic, rgba, stride) && WebPEncode(config, &pic);
+ WebPPictureFree(&pic);
+
+ if (!ok) {
+ WebPMemoryWriterClear(&wrt);
+ return NULL;
+ }
+
+ *output_size = wrt.size;
+ return wrt.mem;
+}
+*/
+import "C"
+import (
+ "errors"
+ "image"
+ "image/draw"
+ "io"
+ "unsafe"
+)
+
+// Encoder stores information to encode image
+type Encoder struct {
+ options *Options
+ config *C.WebPConfig
+ img *image.NRGBA
+}
+
+// NewEncoder return new encoder instance
+func NewEncoder(src image.Image, options *Options) (e *Encoder, err error) {
+ var config *C.WebPConfig
+
+ if options == nil {
+ options, _ = NewLossyEncoderOptions(PresetDefault, 75)
+ }
+ if config, err = options.GetConfig(); err != nil {
+ return nil, err
+ }
+
+ e = &Encoder{options: options, config: config}
+
+ switch v := src.(type) {
+ case *image.NRGBA:
+ e.img = v
+ default:
+ e.img = e.convertToNRGBA(src)
+ }
+
+ return
+}
+
+// Encode picture and flush to io.Writer
+func (e *Encoder) Encode(w io.Writer) error {
+ var size C.size_t
+
+ output := C.encodeNRBBA(
+ e.config,
+ (*C.uint8_t)(&e.img.Pix[0]),
+ C.int(e.img.Rect.Dx()),
+ C.int(e.img.Rect.Dy()),
+ C.int(e.img.Stride),
+ &size,
+ )
+
+ if output == nil || size == 0 {
+ return errors.New("cannot encode webppicture")
+ }
+ defer C.free(unsafe.Pointer(output))
+
+ _, err := w.Write(((*[1 << 30]byte)(unsafe.Pointer(output)))[0:int(size):int(size)])
+
+ return err
+}
+
+// Convert picture from any image.Image type to *image.NRGBA
+// @todo optimization needed
+func (e *Encoder) convertToNRGBA(src image.Image) (dst *image.NRGBA) {
+ dst = image.NewNRGBA(src.Bounds())
+ draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
+
+ return
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/encoder/options.go b/vendor/github.com/kolesa-team/go-webp/encoder/options.go
new file mode 100644
index 00000000..2f8ba34e
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/encoder/options.go
@@ -0,0 +1,214 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package encoder
+
+/*
+#cgo LDFLAGS: -lwebp
+#include
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+)
+
+// Default libwebp image hints
+//noinspection GoUnusedConst
+const (
+ HintDefault ImageHint = iota
+ HintPicture
+ HintPhoto
+ HintGraph
+ HintLast
+)
+
+// Default libwebp presets
+//noinspection GoUnusedConst
+const (
+ PresetDefault EncodingPreset = iota
+ PresetPicture
+ PresetPhoto
+ PresetDrawing
+ PresetIcon
+ PresetText
+)
+
+type (
+ // ImageHint hint of picture
+ ImageHint int
+ // EncodingPreset using Preset
+ EncodingPreset int
+ // Options specifies webp encoding parameters
+ Options struct {
+ config *C.WebPConfig
+
+ Lossless bool
+ Quality float32
+ Method int
+ ImageHint ImageHint
+ TargetSize int
+ TargetPsnr float32
+ Segments int
+ SnsStrength int
+ FilterStrength int
+ FilterSharpness int
+ FilterType int
+ Autofilter bool
+ AlphaCompression int
+ AlphaFiltering int
+ alphaQuality int
+ Pass int
+ // Disabled for compatibility with old version libwebp
+ // QMin int
+ // QMax int
+ ShowCompressed bool
+ Preprocessing int
+ Partitions int
+ PartitionLimit int
+ EmulateJpegSize bool
+ ThreadLevel bool
+ LowMemory bool
+ NearLossless int
+ Exact int
+ UseDeltaPalette bool
+ UseSharpYuv bool
+ }
+)
+
+// NewLossyEncoderOptions build lossy encoding options
+func NewLossyEncoderOptions(preset EncodingPreset, quality float32) (options *Options, err error) {
+ options = &Options{
+ config: &C.WebPConfig{},
+ }
+
+ if C.WebPConfigPreset(options.config, C.WebPPreset(preset), C.float(quality)) == 0 {
+ return nil, errors.New("cannot init encoder config")
+ }
+
+ options.sync()
+
+ return
+}
+
+// NewLosslessEncoderOptions build lossless encoding options
+func NewLosslessEncoderOptions(preset EncodingPreset, level int) (options *Options, err error) {
+ if options, err = NewLossyEncoderOptions(preset, 0); err != nil {
+ return
+ }
+ if C.WebPConfigLosslessPreset(options.config, C.int(level)) == 0 {
+ return nil, errors.New("cannot init lossless preset")
+ }
+
+ options.sync()
+
+ return
+}
+
+func (o *Options) sync() {
+ o.Lossless = o.config.lossless == 1
+ o.Quality = float32(o.config.quality)
+ o.Method = int(o.config.method)
+ o.ImageHint = ImageHint(o.config.image_hint)
+ o.TargetSize = int(o.config.target_size)
+ o.TargetPsnr = float32(o.config.target_PSNR)
+ o.Segments = int(o.config.segments)
+ o.SnsStrength = int(o.config.sns_strength)
+ o.FilterStrength = int(o.config.filter_strength)
+ o.FilterSharpness = int(o.config.filter_sharpness)
+ o.FilterType = int(o.config.filter_type)
+ o.Autofilter = o.config.autofilter == 1
+ o.AlphaCompression = int(o.config.alpha_compression)
+ o.AlphaFiltering = int(o.config.alpha_filtering)
+ o.alphaQuality = int(o.config.alpha_quality)
+ o.Pass = int(o.config.pass)
+ // Disabled for compatibility with old version libwebp
+ // o.QMin = int(o.config.qmin)
+ // o.QMax = int(o.config.qmax)
+ o.ShowCompressed = o.config.show_compressed == 1
+ o.Preprocessing = int(o.config.preprocessing)
+ o.Partitions = int(o.config.partitions)
+ o.PartitionLimit = int(o.config.partition_limit)
+ o.EmulateJpegSize = o.config.emulate_jpeg_size == 1
+ o.ThreadLevel = o.config.thread_level == 1
+ o.LowMemory = o.config.low_memory == 1
+ o.NearLossless = int(o.config.near_lossless)
+ o.Exact = int(o.config.exact)
+ o.UseDeltaPalette = o.config.use_delta_palette == 1
+ o.UseSharpYuv = o.config.use_sharp_yuv == 1
+}
+
+func (o *Options) boolToCInt(expression bool) (result C.int) {
+ result = 0
+
+ if expression {
+ result = 1
+ }
+
+ return
+}
+
+// GetConfig build WebPConfig for libwebp
+func (o *Options) GetConfig() (*C.WebPConfig, error) {
+ var err error
+ if o == nil {
+ o, err = NewLosslessEncoderOptions(PresetDefault, 0)
+ if err != nil {
+ return nil, fmt.Errorf("cannot validate default config: [%w]", err)
+ }
+ }
+ o.config.lossless = o.boolToCInt(o.Lossless)
+ o.config.quality = C.float(o.Quality)
+ o.config.method = C.int(o.Method)
+ o.config.image_hint = C.WebPImageHint(o.ImageHint)
+ o.config.target_size = C.int(o.TargetSize)
+ o.config.target_PSNR = C.float(o.TargetPsnr)
+ o.config.segments = C.int(o.Segments)
+ o.config.sns_strength = C.int(o.SnsStrength)
+ o.config.filter_strength = C.int(o.FilterStrength)
+ o.config.filter_sharpness = C.int(o.FilterSharpness)
+ o.config.filter_type = C.int(o.FilterType)
+ o.config.autofilter = o.boolToCInt(o.Autofilter)
+ o.config.alpha_compression = C.int(o.AlphaCompression)
+ o.config.alpha_filtering = C.int(o.AlphaFiltering)
+ o.config.alpha_quality = C.int(o.alphaQuality)
+ o.config.pass = C.int(o.Pass)
+ // Disabled for compatibility with old version libwebp
+ // o.config.qmin = C.int(o.QMin)
+ // o.config.qmax = C.int(o.QMax)
+ o.config.show_compressed = o.boolToCInt(o.ShowCompressed)
+ o.config.preprocessing = C.int(o.Preprocessing)
+ o.config.partitions = C.int(o.Partitions)
+ o.config.partition_limit = C.int(o.PartitionLimit)
+ o.config.emulate_jpeg_size = o.boolToCInt(o.EmulateJpegSize)
+ o.config.thread_level = o.boolToCInt(o.ThreadLevel)
+ o.config.low_memory = o.boolToCInt(o.LowMemory)
+ o.config.near_lossless = C.int(o.NearLossless)
+ o.config.exact = C.int(o.Exact)
+ o.config.use_delta_palette = o.boolToCInt(o.UseDeltaPalette)
+ o.config.use_sharp_yuv = o.boolToCInt(o.UseSharpYuv)
+
+ if C.WebPValidateConfig(o.config) == 0 {
+ return nil, errors.New("cannot validate config")
+ }
+
+ return o.config, nil
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/utils/vp8.go b/vendor/github.com/kolesa-team/go-webp/utils/vp8.go
new file mode 100644
index 00000000..5b68332b
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/utils/vp8.go
@@ -0,0 +1,111 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package utils
+
+//noinspection GoUnusedConst
+const (
+ Vp8StatusOk VP8StatusCode = iota
+ Vp8StatusOutOfMemory
+ Vp8StatusInvalidParam
+ Vp8StatusBitstreamError
+ Vp8StatusUnsupportedFeature
+ Vp8StatusSuspended
+ Vp8StatusUserAbort
+ Vp8StatusNotEnoughData
+)
+
+type VP8StatusCode int
+
+func (c VP8StatusCode) String() (label string) {
+ switch c {
+ case Vp8StatusOk:
+ label = "VP8_STATUS_OK"
+ case Vp8StatusOutOfMemory:
+ label = "VP8_STATUS_OUT_OF_MEMORY"
+ case Vp8StatusInvalidParam:
+ label = "VP8_STATUS_INVALID_PARAM"
+ case Vp8StatusBitstreamError:
+ label = "VP8_STATUS_BITSTREAM_ERROR"
+ case Vp8StatusUnsupportedFeature:
+ label = "VP8_STATUS_UNSUPPORTED_FEATURE"
+ case Vp8StatusSuspended:
+ label = "VP8_STATUS_SUSPENDED"
+ case Vp8StatusUserAbort:
+ label = "VP8_STATUS_USER_ABORT"
+ case Vp8StatusNotEnoughData:
+ label = "VP8_STATUS_NOT_ENOUGH_DATA"
+ default:
+ label = "VP8 undefined status code"
+ }
+
+ return
+}
+
+const (
+ Vp8EncOk Vp8EncStatus = iota
+ Vp8EncErrorOutOfMemory
+ Vp8EncErrorBitstreamOutOfMemory
+ Vp8EncErrorNullParameter
+ Vp8EncErrorInvalidConfiguration
+ Vp8EncErrorBadDimension
+ Vp8EncErrorPartition0Overflow
+ Vp8EncErrorPartitionOverflow
+ Vp8EncErrorBadWrite
+ Vp8EncErrorFileTooBig
+ Vp8EncErrorUserAbort
+ Vp8EncErrorLast
+)
+
+type Vp8EncStatus int
+
+func (c Vp8EncStatus) String() (label string) {
+ switch c {
+ case Vp8EncOk:
+ label = "VP8_ENC_OK"
+ case Vp8EncErrorOutOfMemory:
+ label = "VP8_ENC_ERROR_OUT_OF_MEMORY"
+ case Vp8EncErrorBitstreamOutOfMemory:
+ label = "VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY"
+ case Vp8EncErrorNullParameter:
+ label = "VP8_ENC_ERROR_NULL_PARAMETER"
+ case Vp8EncErrorInvalidConfiguration:
+ label = "VP8_ENC_ERROR_INVALID_CONFIGURATION"
+ case Vp8EncErrorBadDimension:
+ label = "VP8_ENC_ERROR_BAD_DIMENSION"
+ case Vp8EncErrorPartition0Overflow:
+ label = "VP8_ENC_ERROR_PARTITION0_OVERFLOW"
+ case Vp8EncErrorPartitionOverflow:
+ label = "VP8_ENC_ERROR_PARTITION_OVERFLOW"
+ case Vp8EncErrorBadWrite:
+ label = "VP8_ENC_ERROR_BAD_WRITE"
+ case Vp8EncErrorFileTooBig:
+ label = "VP8_ENC_ERROR_FILE_TOO_BIG"
+ case Vp8EncErrorUserAbort:
+ label = "VP8_ENC_ERROR_USER_ABORT"
+ case Vp8EncErrorLast:
+ label = "VP8_ENC_ERROR_LAST"
+ default:
+ label = "VP8 undefined status code"
+ }
+
+ return
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/utils/webp.go b/vendor/github.com/kolesa-team/go-webp/utils/webp.go
new file mode 100644
index 00000000..d1bd1acf
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/utils/webp.go
@@ -0,0 +1,39 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package utils
+
+type FormatType int
+
+//noinspection GoUnusedConst
+const (
+ FormatUndefined FormatType = iota
+ FormatLossy
+ FormatLossless
+)
+
+type BitstreamFeatures struct {
+ Width int
+ Height int
+ HasAlpha bool
+ HasAnimation bool
+ Format FormatType
+}
diff --git a/vendor/github.com/kolesa-team/go-webp/webp/webp.go b/vendor/github.com/kolesa-team/go-webp/webp/webp.go
new file mode 100644
index 00000000..6bf5ebde
--- /dev/null
+++ b/vendor/github.com/kolesa-team/go-webp/webp/webp.go
@@ -0,0 +1,72 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2019 Amangeldy Kadyl
+//
+// 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.
+
+package webp
+
+import (
+ "github.com/kolesa-team/go-webp/decoder"
+ "github.com/kolesa-team/go-webp/encoder"
+ "image"
+ "image/color"
+ "io"
+)
+
+func init() {
+ image.RegisterFormat("webp", "RIFF????WEBPVP8", quickDecode, quickDecodeConfig)
+}
+
+func quickDecode(r io.Reader) (image.Image, error) {
+ return Decode(r, &decoder.Options{})
+}
+
+func quickDecodeConfig(r io.Reader) (image.Config, error) {
+ return DecodeConfig(r, &decoder.Options{})
+}
+
+// Decode picture from reader
+func Decode(r io.Reader, options *decoder.Options) (image.Image, error) {
+ if dec, err := decoder.NewDecoder(r, options); err != nil {
+ return nil, err
+ } else {
+ return dec.Decode()
+ }
+}
+
+func DecodeConfig(r io.Reader, options *decoder.Options) (image.Config, error) {
+ if dec, err := decoder.NewDecoder(r, options); err != nil {
+ return image.Config{}, err
+ } else {
+ return image.Config{
+ ColorModel: color.RGBAModel,
+ Width: dec.GetFeatures().Width,
+ Height: dec.GetFeatures().Height,
+ }, nil
+ }
+}
+
+// Encode picture and write to io.Writer
+func Encode(w io.Writer, src image.Image, options *encoder.Options) error {
+ if enc, err := encoder.NewEncoder(src, options); err != nil {
+ return err
+ } else {
+ return enc.Encode(w)
+ }
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 33964926..5ba47436 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -37,6 +37,12 @@ github.com/joho/godotenv
# github.com/justinas/alice v1.2.0
## explicit; go 1.12
github.com/justinas/alice
+# github.com/kolesa-team/go-webp v1.0.4
+## explicit; go 1.10
+github.com/kolesa-team/go-webp/decoder
+github.com/kolesa-team/go-webp/encoder
+github.com/kolesa-team/go-webp/utils
+github.com/kolesa-team/go-webp/webp
# github.com/leodido/go-urn v1.2.4
## explicit; go 1.16
github.com/leodido/go-urn