Repository: kolesa-team/go-webp
Branch: master
Commit: bf7924d9a4e2
Files: 23
Total size: 47.2 KB
Directory structure:
gitextract_hax6w2m8/
├── .gitignore
├── .travis.yml
├── AUTHORS
├── LICENSE
├── Makefile
├── README.md
├── _examples/
│ ├── decode/
│ │ └── main.go
│ └── encode/
│ └── main.go
├── codecov.yml
├── decoder/
│ ├── decoder.go
│ ├── decoder_test.go
│ ├── options.go
│ └── options_test.go
├── encoder/
│ ├── encoder.go
│ ├── encoder_test.go
│ ├── options.go
│ └── options_test.go
├── go.mod
├── go.sum
├── utils/
│ ├── vp8.go
│ └── webp.go
└── webp/
├── webp.go
└── webp_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/.idea
/vendor
.DS_Store
/example/output*
================================================
FILE: .travis.yml
================================================
dist: bionic
language: go
go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
- 1.x
script:
- go vet ./...
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
before_install:
- sudo apt-get install libwebp-dev
- go mod vendor -v
after_success:
- bash <(curl -s https://codecov.io/bash)
addons:
apt:
update: true
================================================
FILE: AUTHORS
================================================
Google LLC (https://opensource.google.com/)
Amangeldy Kadyl <lex0.kz@gmail.com>
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: Makefile
================================================
.PHONY: vendor test
GO ?= $(which go)
test:
@GO test ./... -v
vendor:
@GO mod vendor
vet:
@GO vet ./... -v
bench:
@GO test -bench=. ./webp
libwebp-mac:
brew install webp
libwebp-ubuntu:
sudo apt-get install libwebp-dev
================================================
FILE: README.md
================================================

# go-webp
[](https://travis-ci.com/github/kolesa-team/go-webp)
[](https://godoc.org/github.com/kolesa-team/go-webp)
[](https://goreportcard.com/report/github.com/kolesa-team/go-webp)
[](https://codecov.io/gh/kolesa-team/go-webp)
Golang Webp library for encoding and decoding, using **C** binding for Google libwebp
## Requirements
[libwebp](https://developers.google.com/speed/webp/docs/api)
## Benchmarks
```text
% go test -bench "^BenchmarkDecode" ./webp
goos: darwin
goarch: amd64
pkg: github.com/kolesa-team/go-webp/webp
BenchmarkDecodeLossy-12 45 25965139 ns/op
BenchmarkDecodeXImageLossy-12 13 90735879 ns/op
BenchmarkDecodeLossless-12 64 18887482 ns/op
BenchmarkDecodeXImageLossless-12 27 42422596 ns/op
PASS
ok github.com/kolesa-team/go-webp/webp 7.877s
```
## Install libwebp
#### MacOS:
```bash
brew install webp
```
#### Linux:
```bash
sudo apt-get update
sudo apt-get install libwebp-dev
```
## Install
`go get -u github.com/kolesa-team/go-webp`
## Examples
#### Decode:
```go
package main
import (
"image/jpeg"
"log"
"os"
"github.com/kolesa-team/go-webp/decoder"
"github.com/kolesa-team/go-webp/webp"
)
func main() {
file, err := os.Open("test_data/images/m4_q75.webp")
if err != nil {
log.Fatalln(err)
}
output, err := os.Create("example/output_decode.jpg")
if err != nil {
log.Fatal(err)
}
defer output.Close()
img, err := webp.Decode(file, &decoder.Options{})
if err != nil {
log.Fatalln(err)
}
if err = jpeg.Encode(output, img, &jpeg.Options{Quality:75}); err != nil {
log.Fatalln(err)
}
}
```
```bash
go run example/decode/main.go
```
#### Encode
```go
package main
import (
"github.com/kolesa-team/go-webp/encoder"
"github.com/kolesa-team/go-webp/webp"
"image/jpeg"
"log"
"os"
)
func main() {
file, err := os.Open("test_data/images/source.jpg")
if err != nil {
log.Fatalln(err)
}
img, err := jpeg.Decode(file)
if err != nil {
log.Fatalln(err)
}
output, err := os.Create("example/output_decode.webp")
if err != nil {
log.Fatal(err)
}
defer output.Close()
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
log.Fatalln(err)
}
if err := webp.Encode(output, img, options); err != nil {
log.Fatalln(err)
}
}
```
```bash
go run example/encode/main.go
```
## TODO
- return aux stats
- container api
- incremental decoding
## License
MIT licensed. See the LICENSE file for details.
================================================
FILE: _examples/decode/main.go
================================================
// 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 main
import (
"image/jpeg"
"log"
"os"
"github.com/kolesa-team/go-webp/decoder"
"github.com/kolesa-team/go-webp/webp"
)
func main() {
file, err := os.Open("test_data/images/m4_q75.webp")
if err != nil {
log.Fatalln(err)
}
output, err := os.Create("example/output_decode.jpg")
if err != nil {
log.Fatal(err)
}
//noinspection GoUnhandledErrorResult
defer output.Close()
img, err := webp.Decode(file, &decoder.Options{})
if err != nil {
log.Fatalln(err)
}
if err = jpeg.Encode(output, img, &jpeg.Options{Quality: 75}); err != nil {
log.Fatalln(err)
}
}
================================================
FILE: _examples/encode/main.go
================================================
// 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 main
import (
"github.com/kolesa-team/go-webp/encoder"
"github.com/kolesa-team/go-webp/webp"
"image/jpeg"
"log"
"os"
)
func main() {
file, err := os.Open("test_data/images/source.jpg")
if err != nil {
log.Fatalln(err)
}
img, err := jpeg.Decode(file)
if err != nil {
log.Fatalln(err)
}
output, err := os.Create("example/output_encode.webp")
if err != nil {
log.Fatal(err)
}
//noinspection GoUnhandledErrorResult
defer output.Close()
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
log.Fatalln(err)
}
if err := webp.Encode(output, img, options); err != nil {
log.Fatalln(err)
}
}
================================================
FILE: codecov.yml
================================================
ignore:
- "_examples"
- "utils"
- "test_data"
coverage:
status:
project:
default:
target: 80%
threshold: 1%
================================================
FILE: decoder/decoder.go
================================================
// 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
#cgo windows LDFLAGS: -lwebp
#include <stdlib.h>
#include <webp/decode.h>
*/
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
}
================================================
FILE: decoder/decoder_test.go
================================================
// 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
import (
"github.com/kolesa-team/go-webp/utils"
"github.com/stretchr/testify/require"
"image"
"os"
"testing"
)
func TestNewDecoder(t *testing.T) {
t.Run("create success", func(t *testing.T) {
file, err := os.Open("../test_data/images/m4_q75.webp")
if err != nil {
t.Fatal(err)
}
if d, err := NewDecoder(file, &Options{
BypassFiltering: true,
NoFancyUpsampling: true,
Scale: image.Rectangle{
Max: image.Point{
X: 300,
Y: 300,
},
},
UseThreads: true,
Flip: true,
DitheringStrength: 0,
AlphaDitheringStrength: 0,
}); err != nil {
t.Fatal(err)
} else if img, err := d.Decode(); err != nil {
t.Fatal(err)
} else if img.Bounds().Max.X <= 0 || img.Bounds().Max.Y <= 0 {
t.Fatal("invalid decoding result")
}
})
t.Run("empty file", func(t *testing.T) {
file, err := os.Open("../test_data/images/invalid.webp")
if err != nil {
t.Fatal(err)
}
if _, err := NewDecoder(file, &Options{
Crop: image.Rectangle{
Max: image.Point{
X: 150,
Y: 150,
},
},
}); err == nil {
t.Fatal(err)
}
})
}
func TestDecoder_GetFeatures(t *testing.T) {
file, err := os.Open("../test_data/images/m4_q75.webp")
if err != nil {
t.Fatal(err)
}
dec, err := NewDecoder(file, &Options{})
if err != nil {
t.Fatal(err)
}
features := dec.GetFeatures()
if features.Width != 675 || features.Height != 900 {
t.Fatal("incorrect dimensions")
}
if features.Format != utils.FormatLossy {
t.Fatal("file format is invalid")
}
if features.HasAlpha {
t.Fatal("file has_alpha is invalid")
}
if features.HasAlpha {
t.Fatal("file has_animation is invalid")
}
}
func TestDecoder_NilOptions(t *testing.T) {
file, err := os.Open("../test_data/images/m4_q75.webp")
require.NoError(t, err)
_, err = NewDecoder(file, nil)
require.NoError(t, err)
}
================================================
FILE: decoder/options.go
================================================
// 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 <webp/decode.h>
*/
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
}
================================================
FILE: decoder/options_test.go
================================================
// 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
import (
"image"
"testing"
)
func TestOptions_GetConfig(t *testing.T) {
t.Run("check crop", func(t *testing.T) {
options := &Options{
Crop: image.Rectangle{
Min: image.Point{X: 100, Y: 200},
Max: image.Point{X: 400, Y: 500},
},
}
if cfg, err := options.GetConfig(); err != nil {
t.Fatal(err)
} else if cfg.options.use_cropping != 1 {
t.Fatal("cropping is disabled")
} else if cfg.options.crop_left != 100 {
t.Fatal("crop_left is invalid")
} else if cfg.options.crop_top != 200 {
t.Fatal("crop_top is invalid")
} else if cfg.options.crop_width != 400 {
t.Fatal("crop_width is invalid")
} else if cfg.options.crop_height != 500 {
t.Fatal("crop_height is invalid")
}
})
t.Run("check scale", func(t *testing.T) {
options := &Options{
Scale: image.Rectangle{
Max: image.Point{X: 400, Y: 500},
},
}
if cfg, err := options.GetConfig(); err != nil {
t.Fatal(err)
} else if cfg.options.use_scaling != 1 {
t.Fatal("scaling is disabled")
} else if cfg.options.scaled_width != 400 {
t.Fatal("scaled_width is invalid")
} else if cfg.options.scaled_height != 500 {
t.Fatal("scaled_height is invalid")
}
})
}
================================================
FILE: encoder/encoder.go
================================================
// 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
#cgo windows LDFLAGS: -lwebp
#include <stdlib.h>
#include <webp/encode.h>
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
}
================================================
FILE: encoder/encoder_test.go
================================================
package encoder
import (
"bytes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"image"
"os"
"testing"
)
func TestNewEncoder(t *testing.T) {
t.Run("encode nrgba image with lossy preset", func(t *testing.T) {
expected := &bytes.Buffer{}
img := image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 100,
Y: 150,
},
})
options, err := NewLossyEncoderOptions(PresetDefault, 0.75)
require.NoError(t, err)
e, err := NewEncoder(img, options)
require.NoError(t, err)
err = e.Encode(expected)
require.NoError(t, err)
actual, err := os.ReadFile("../test_data/images/100x150_lossy.webp")
require.NoError(t, err)
assert.Equal(t, actual, expected.Bytes())
})
t.Run("encode nrgba image with lossless preset", func(t *testing.T) {
actuall := &bytes.Buffer{}
img := image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 100,
Y: 150,
},
})
options, err := NewLosslessEncoderOptions(PresetDefault, 1)
require.NoError(t, err)
e, err := NewEncoder(img, options)
require.NoError(t, err)
err = e.Encode(actuall)
require.NoError(t, err)
expected, err := os.ReadFile("../test_data/images/100x150_lossless.webp")
require.NoError(t, err)
assert.Equal(t, expected, actuall.Bytes())
})
}
func TestNewEncoder_NilOptions(t *testing.T) {
t.Run("encode image without passing options should not panic", func(t *testing.T) {
expected := &bytes.Buffer{}
img := image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 100,
Y: 150,
},
})
e, err := NewEncoder(img, nil)
require.NoError(t, err)
err = e.Encode(expected)
require.NoError(t, err)
})
}
================================================
FILE: encoder/options.go
================================================
// 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 <webp/encode.h>
*/
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
}
================================================
FILE: encoder/options_test.go
================================================
package encoder
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestNilOptions(t *testing.T) {
t.Run("config from nil Options succeeds", func(t *testing.T) {
var options *Options = nil
cfg, err := options.GetConfig()
require.NoError(t, err)
require.NotNil(t, cfg)
})
}
func TestNewLossyEncoderOptions(t *testing.T) {
t.Run("invalid quality", func(t *testing.T) {
options, err := NewLossyEncoderOptions(PresetDefault, -1)
require.Error(t, err)
assert.Nil(t, options)
})
t.Run("create lossy encoder options is success", func(t *testing.T) {
options, err := NewLossyEncoderOptions(PresetDefault, 75)
require.Nil(t, err)
require.NotNil(t, options)
assert.False(t, options.Lossless)
assert.Equal(t, float32(75), options.Quality)
cfg, err := options.GetConfig()
require.NoError(t, err)
assert.NotNil(t, cfg)
})
}
func TestNewLosslessEncoderOptions(t *testing.T) {
t.Run("invalid level of lossless encoder", func(t *testing.T) {
options, err := NewLosslessEncoderOptions(PresetDefault, -1)
require.Error(t, err)
assert.Nil(t, options)
})
t.Run("create lossless encoder options is success", func(t *testing.T) {
options, err := NewLosslessEncoderOptions(PresetDefault, 1)
require.Nil(t, err)
require.NotNil(t, options)
assert.True(t, options.Lossless)
assert.Equal(t, 1, options.Method)
assert.Equal(t, float32(20), options.Quality)
cfg, err := options.GetConfig()
require.NoError(t, err)
assert.NotNil(t, cfg)
})
}
================================================
FILE: go.mod
================================================
module github.com/kolesa-team/go-webp
go 1.10
require github.com/stretchr/testify v1.7.0
================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: utils/vp8.go
================================================
// 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
}
================================================
FILE: utils/webp.go
================================================
// 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
}
================================================
FILE: webp/webp.go
================================================
// 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)
}
}
================================================
FILE: webp/webp_test.go
================================================
// 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 (
"bytes"
"github.com/kolesa-team/go-webp/decoder"
"github.com/kolesa-team/go-webp/encoder"
"github.com/stretchr/testify/require"
"image"
"image/color"
"image/jpeg"
"io"
"os"
"testing"
)
func TestEncode(t *testing.T) {
t.Run("encode lossy", func(t *testing.T) {
r, err := os.Open("../test_data/images/source.jpg")
if err != nil {
t.Fatal(err)
}
img, err := jpeg.Decode(r)
if err != nil {
t.Fatal(err)
}
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
t.Fatal(err)
}
if err = Encode(io.Discard, img, options); err != nil {
t.Fatal(err)
}
})
t.Run("encode lossless", func(t *testing.T) {
r, err := os.Open("../test_data/images/source.jpg")
if err != nil {
t.Fatal(err)
}
img, err := jpeg.Decode(r)
if err != nil {
t.Fatal(err)
}
options, err := encoder.NewLosslessEncoderOptions(encoder.PresetDefault, 4)
if err != nil {
t.Fatal(err)
}
if err = Encode(io.Discard, img, options); err != nil {
t.Fatal(err)
}
})
t.Run("failed encoding with large size", func(t *testing.T) {
img := image.NewNRGBA(image.Rectangle{
Max: image.Point{X: 20000, Y: 1},
})
options, err := encoder.NewLosslessEncoderOptions(encoder.PresetDefault, 4)
if err != nil {
t.Fatal(err)
}
if err = Encode(io.Discard, img, options); err == nil {
t.Fatal("an error was expected")
}
})
}
func TestDecode(t *testing.T) {
t.Run("decode lossy webp picture with many options", func(t *testing.T) {
r, err := os.Open("../test_data/images/webp-logo-lossy.webp")
if err != nil {
t.Fatal(err)
}
if img, err := Decode(r, &decoder.Options{
BypassFiltering: true,
NoFancyUpsampling: true,
Crop: image.Rectangle{},
Scale: image.Rectangle{
Max: image.Point{
X: 400,
Y: 300,
},
},
UseThreads: true,
Flip: true,
DitheringStrength: 1,
AlphaDitheringStrength: 1,
}); err != nil {
t.Fatal(err)
} else if img == nil {
t.Fatal("img is empty")
} else if img.Bounds().Max.X != 400 || img.Bounds().Max.Y != 300 {
t.Fatal("img is invalid")
}
})
t.Run("decode lossless webp picture", func(t *testing.T) {
r, err := os.Open("../test_data/images/webp-logo-lossless.webp")
if err != nil {
t.Fatal(err)
}
if img, err := Decode(r, &decoder.Options{}); err != nil {
t.Fatal(err)
} else if img == nil {
t.Fatal("img is empty")
}
})
t.Run("decode invalid webp file", func(t *testing.T) {
r, err := os.Open("../test_data/images/invalid.webp")
if err != nil {
t.Fatal(err)
}
if _, err := Decode(r, &decoder.Options{}); err == nil {
t.Fatal("an error was expected")
}
})
t.Run("decode lossy image via standard image.Decode", func(t *testing.T) {
r, err := os.Open("../test_data/images/webp-logo-lossy.webp")
require.NoError(t, err)
img, format, err := image.Decode(r)
require.NoError(t, err)
require.Equal(t, "webp", format)
require.Equal(t, color.NRGBAModel, img.ColorModel())
})
t.Run("decode lossless image via standard image.Decode", func(t *testing.T) {
r, err := os.Open("../test_data/images/webp-logo-lossless.webp")
require.NoError(t, err)
img, format, err := image.Decode(r)
require.NoError(t, err)
require.Equal(t, "webp", format)
require.Equal(t, color.NRGBAModel, img.ColorModel())
})
}
func BenchmarkDecodeLossy(b *testing.B) {
data, err := os.ReadFile("../test_data/images/webp-logo-lossy.webp")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
dec, err := decoder.NewDecoder(bytes.NewBuffer(data), &decoder.Options{})
if err != nil {
b.Fatal(err)
}
if _, err := dec.Decode(); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeXImageLossy(b *testing.B) {
data, err := os.ReadFile("../test_data/images/webp-logo-lossy.webp")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
_, err = Decode(bytes.NewBuffer(data), &decoder.Options{})
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeLossless(b *testing.B) {
data, err := os.ReadFile("../test_data/images/webp-logo-lossless.webp")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
dec, err := decoder.NewDecoder(bytes.NewBuffer(data), &decoder.Options{})
if err != nil {
b.Fatal(err)
}
if _, err := dec.Decode(); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeXImageLossless(b *testing.B) {
data, err := os.ReadFile("../test_data/images/webp-logo-lossless.webp")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
_, err = Decode(bytes.NewBuffer(data), &decoder.Options{})
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkEncode(b *testing.B) {
r, err := os.Open("../test_data/images/source.jpg")
if err != nil {
b.Fatal(err)
}
img, err := jpeg.Decode(r)
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
b.Fatal(err)
}
if err = Encode(io.Discard, img, options); err != nil {
b.Fatal(err)
}
}
}
gitextract_hax6w2m8/
├── .gitignore
├── .travis.yml
├── AUTHORS
├── LICENSE
├── Makefile
├── README.md
├── _examples/
│ ├── decode/
│ │ └── main.go
│ └── encode/
│ └── main.go
├── codecov.yml
├── decoder/
│ ├── decoder.go
│ ├── decoder_test.go
│ ├── options.go
│ └── options_test.go
├── encoder/
│ ├── encoder.go
│ ├── encoder_test.go
│ ├── options.go
│ └── options_test.go
├── go.mod
├── go.sum
├── utils/
│ ├── vp8.go
│ └── webp.go
└── webp/
├── webp.go
└── webp_test.go
SYMBOL INDEX (84 symbols across 14 files)
FILE: _examples/decode/main.go
function main (line 33) | func main() {
FILE: _examples/encode/main.go
function main (line 32) | func main() {
FILE: decoder/decoder.go
type Decoder (line 43) | type Decoder struct
method Decode (line 84) | func (d *Decoder) Decode() (image.Image, error) {
method GetFeatures (line 109) | func (d *Decoder) GetFeatures() utils.BitstreamFeatures {
method parseFeatures (line 120) | func (d *Decoder) parseFeatures(dataPtr *C.uint8_t, sizePtr C.size_t) ...
method getOutputDimensions (line 125) | func (d *Decoder) getOutputDimensions() (width, height C.int) {
function NewDecoder (line 52) | func NewDecoder(r io.Reader, options *Options) (d *Decoder, err error) {
FILE: decoder/decoder_test.go
function TestNewDecoder (line 32) | func TestNewDecoder(t *testing.T) {
function TestDecoder_GetFeatures (line 79) | func TestDecoder_GetFeatures(t *testing.T) {
function TestDecoder_NilOptions (line 109) | func TestDecoder_NilOptions(t *testing.T) {
FILE: decoder/options.go
type Options (line 35) | type Options struct
method GetConfig (line 47) | func (o *Options) GetConfig() (*C.WebPDecoderConfig, error) {
FILE: decoder/options_test.go
function TestOptions_GetConfig (line 29) | func TestOptions_GetConfig(t *testing.T) {
FILE: encoder/encoder.go
type Encoder (line 69) | type Encoder struct
method Encode (line 99) | func (e *Encoder) Encode(w io.Writer) error {
method convertToNRGBA (line 123) | func (e *Encoder) convertToNRGBA(src image.Image) (dst *image.NRGBA) {
function NewEncoder (line 76) | func NewEncoder(src image.Image, options *Options) (e *Encoder, err erro...
FILE: encoder/encoder_test.go
function TestNewEncoder (line 12) | func TestNewEncoder(t *testing.T) {
function TestNewEncoder_NilOptions (line 61) | func TestNewEncoder_NilOptions(t *testing.T) {
FILE: encoder/options.go
constant HintDefault (line 37) | HintDefault ImageHint = iota
constant HintPicture (line 38) | HintPicture
constant HintPhoto (line 39) | HintPhoto
constant HintGraph (line 40) | HintGraph
constant HintLast (line 41) | HintLast
constant PresetDefault (line 47) | PresetDefault EncodingPreset = iota
constant PresetPicture (line 48) | PresetPicture
constant PresetPhoto (line 49) | PresetPhoto
constant PresetDrawing (line 50) | PresetDrawing
constant PresetIcon (line 51) | PresetIcon
constant PresetText (line 52) | PresetText
type ImageHint (line 57) | type ImageHint
type EncodingPreset (line 59) | type EncodingPreset
type Options (line 61) | type Options struct
method sync (line 126) | func (o *Options) sync() {
method boolToCInt (line 159) | func (o *Options) boolToCInt(expression bool) (result C.int) {
method GetConfig (line 170) | func (o *Options) GetConfig() (*C.WebPConfig, error) {
function NewLossyEncoderOptions (line 98) | func NewLossyEncoderOptions(preset EncodingPreset, quality float32) (opt...
function NewLosslessEncoderOptions (line 113) | func NewLosslessEncoderOptions(preset EncodingPreset, level int) (option...
FILE: encoder/options_test.go
function TestNilOptions (line 9) | func TestNilOptions(t *testing.T) {
function TestNewLossyEncoderOptions (line 18) | func TestNewLossyEncoderOptions(t *testing.T) {
function TestNewLosslessEncoderOptions (line 38) | func TestNewLosslessEncoderOptions(t *testing.T) {
FILE: utils/vp8.go
constant Vp8StatusOk (line 26) | Vp8StatusOk VP8StatusCode = iota
constant Vp8StatusOutOfMemory (line 27) | Vp8StatusOutOfMemory
constant Vp8StatusInvalidParam (line 28) | Vp8StatusInvalidParam
constant Vp8StatusBitstreamError (line 29) | Vp8StatusBitstreamError
constant Vp8StatusUnsupportedFeature (line 30) | Vp8StatusUnsupportedFeature
constant Vp8StatusSuspended (line 31) | Vp8StatusSuspended
constant Vp8StatusUserAbort (line 32) | Vp8StatusUserAbort
constant Vp8StatusNotEnoughData (line 33) | Vp8StatusNotEnoughData
type VP8StatusCode (line 36) | type VP8StatusCode
method String (line 38) | func (c VP8StatusCode) String() (label string) {
constant Vp8EncOk (line 64) | Vp8EncOk Vp8EncStatus = iota
constant Vp8EncErrorOutOfMemory (line 65) | Vp8EncErrorOutOfMemory
constant Vp8EncErrorBitstreamOutOfMemory (line 66) | Vp8EncErrorBitstreamOutOfMemory
constant Vp8EncErrorNullParameter (line 67) | Vp8EncErrorNullParameter
constant Vp8EncErrorInvalidConfiguration (line 68) | Vp8EncErrorInvalidConfiguration
constant Vp8EncErrorBadDimension (line 69) | Vp8EncErrorBadDimension
constant Vp8EncErrorPartition0Overflow (line 70) | Vp8EncErrorPartition0Overflow
constant Vp8EncErrorPartitionOverflow (line 71) | Vp8EncErrorPartitionOverflow
constant Vp8EncErrorBadWrite (line 72) | Vp8EncErrorBadWrite
constant Vp8EncErrorFileTooBig (line 73) | Vp8EncErrorFileTooBig
constant Vp8EncErrorUserAbort (line 74) | Vp8EncErrorUserAbort
constant Vp8EncErrorLast (line 75) | Vp8EncErrorLast
type Vp8EncStatus (line 78) | type Vp8EncStatus
method String (line 80) | func (c Vp8EncStatus) String() (label string) {
FILE: utils/webp.go
type FormatType (line 24) | type FormatType
constant FormatUndefined (line 28) | FormatUndefined FormatType = iota
constant FormatLossy (line 29) | FormatLossy
constant FormatLossless (line 30) | FormatLossless
type BitstreamFeatures (line 33) | type BitstreamFeatures struct
FILE: webp/webp.go
function init (line 32) | func init() {
function quickDecode (line 36) | func quickDecode(r io.Reader) (image.Image, error) {
function quickDecodeConfig (line 40) | func quickDecodeConfig(r io.Reader) (image.Config, error) {
function Decode (line 45) | func Decode(r io.Reader, options *decoder.Options) (image.Image, error) {
function DecodeConfig (line 53) | func DecodeConfig(r io.Reader, options *decoder.Options) (image.Config, ...
function Encode (line 66) | func Encode(w io.Writer, src image.Image, options *encoder.Options) error {
FILE: webp/webp_test.go
function TestEncode (line 37) | func TestEncode(t *testing.T) {
function TestDecode (line 94) | func TestDecode(t *testing.T) {
function BenchmarkDecodeLossy (line 167) | func BenchmarkDecodeLossy(b *testing.B) {
function BenchmarkDecodeXImageLossy (line 185) | func BenchmarkDecodeXImageLossy(b *testing.B) {
function BenchmarkDecodeLossless (line 199) | func BenchmarkDecodeLossless(b *testing.B) {
function BenchmarkDecodeXImageLossless (line 217) | func BenchmarkDecodeXImageLossless(b *testing.B) {
function BenchmarkEncode (line 231) | func BenchmarkEncode(b *testing.B) {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
{
"path": ".gitignore",
"chars": 42,
"preview": "/.idea\n/vendor\n.DS_Store\n/example/output*\n"
},
{
"path": ".travis.yml",
"chars": 345,
"preview": "dist: bionic\nlanguage: go\ngo:\n - 1.13.x\n - 1.14.x\n - 1.15.x\n - 1.16.x\n - 1.x\n\nscript:\n - go vet ./...\n - go test "
},
{
"path": "AUTHORS",
"chars": 80,
"preview": "Google LLC (https://opensource.google.com/)\nAmangeldy Kadyl <lex0.kz@gmail.com>\n"
},
{
"path": "LICENSE",
"chars": 1082,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2019 Amangeldy Kadyl\n\nPermission is hereby granted, free of charge, to any person o"
},
{
"path": "Makefile",
"chars": 232,
"preview": ".PHONY: vendor test\n\nGO ?= $(which go)\n\ntest:\n\t@GO test ./... -v\n\nvendor:\n\t@GO mod vendor\n\nvet:\n\t@GO vet ./... -v\n\nbench"
},
{
"path": "README.md",
"chars": 2948,
"preview": "\n# go-webp\n[\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "_examples/encode/main.go",
"chars": 1804,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "codecov.yml",
"chars": 142,
"preview": "ignore:\n - \"_examples\"\n - \"utils\"\n - \"test_data\"\ncoverage:\n status:\n project:\n default:\n target: 80%\n"
},
{
"path": "decoder/decoder.go",
"chars": 4175,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "decoder/decoder_test.go",
"chars": 3039,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "decoder/options.go",
"chars": 2753,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "decoder/options_test.go",
"chars": 2343,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "encoder/encoder.go",
"chars": 3311,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "encoder/encoder_test.go",
"chars": 1664,
"preview": "package encoder\n\nimport (\n\t\"bytes\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"image\""
},
{
"path": "encoder/options.go",
"chars": 6559,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "encoder/options_test.go",
"chars": 1536,
"preview": "package encoder\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"testing\"\n)\n\nfun"
},
{
"path": "go.mod",
"chars": 91,
"preview": "module github.com/kolesa-team/go-webp\n\ngo 1.10\n\nrequire github.com/stretchr/testify v1.7.0\n"
},
{
"path": "go.sum",
"chars": 1024,
"preview": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.m"
},
{
"path": "utils/vp8.go",
"chars": 3365,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "utils/webp.go",
"chars": 1412,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "webp/webp.go",
"chars": 2362,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
},
{
"path": "webp/webp_test.go",
"chars": 6252,
"preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2019 Amangeldy Kadyl\n//\n// Permission is hereby granted, free of charge, to"
}
]
About this extraction
This page contains the full source code of the kolesa-team/go-webp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (47.2 KB), approximately 13.6k tokens, and a symbol index with 84 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.