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