@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
|
||||||
|
/examples/out/*
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2015, Shunsuke MICHII
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||||
|
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
go-libwebp
|
||||||
|
==========
|
||||||
|
|
||||||
|
A implementation of Go binding for [libwebp](https://developers.google.com/speed/webp/docs/api) written with cgo.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The [examples](./examples) directory contains example codes and images.
|
||||||
|
|
||||||
|
### Decoding WebP into image.RGBA
|
||||||
|
|
||||||
|
```
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/harukasan/go-libwebp/examples/util"
|
||||||
|
"github.com/harukasan/go-libwebp/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Read binary data
|
||||||
|
data, err := ioutil.ReadFile("examples/cosmos.webp")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
options := &webp.DecoderOptions{}
|
||||||
|
img, err := webp.DecodeRGBA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.WritePNG(img, "out/encoded_cosmos.png")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set more decoding options such as cropping, flipping and scaling.
|
||||||
|
|
||||||
|
### Encoding WebP from image.RGBA
|
||||||
|
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/harukasan/go-libwebp/examples/util"
|
||||||
|
"github.com/harukasan/go-libwebp/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := util.ReadPNG("examples/cosmos.png")
|
||||||
|
if err != nil {
|
||||||
|
panic()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to WebP
|
||||||
|
io := ("out.webp")
|
||||||
|
w := bufio.NewWriter(f)
|
||||||
|
defer func() {
|
||||||
|
w.Flush()
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := webp.EncodeRGBA(w, img.(*image.RGBA), config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Incremental decoding API
|
||||||
|
- Container API (Animation)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2014 MICHII Shunsuke. All rights reserved.
|
||||||
|
|
||||||
|
This library is released under The BSD 2-Clause License.
|
||||||
|
See [LICENSE](./LICENSE).
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
Example Codes and Images
|
||||||
|
========================
|
||||||
|
|
||||||
|
- [decode/decode.go](./decode/decode.go) -- a example code to decoding WebP image into image.RGBA.
|
||||||
|
- [encode/encode.go](./encode/encode.go) -- a example code to encoding and writing image.RGBA into WebP file.
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/harukasan/go-libwebp/examples/util"
|
||||||
|
"github.com/harukasan/go-libwebp/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Read binary data
|
||||||
|
data := util.ReadFile("cosmos.webp")
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
options := &webp.DecoderOptions{}
|
||||||
|
img, err := webp.DecodeRGBA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WritePNG(img, "encoded_cosmos.png")
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/harukasan/go-libwebp/examples/util"
|
||||||
|
"github.com/harukasan/go-libwebp/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
img := util.ReadPNG("cosmos.png")
|
||||||
|
|
||||||
|
// Create file and buffered writer
|
||||||
|
io := util.CreateFile("encoded_cosmos.webp")
|
||||||
|
w := bufio.NewWriter(io)
|
||||||
|
defer func() {
|
||||||
|
w.Flush()
|
||||||
|
io.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := webp.Config{
|
||||||
|
Preset: webp.PresetDefault,
|
||||||
|
Quality: 90,
|
||||||
|
Method: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode into WebP
|
||||||
|
if err := webp.EncodeRGBA(w, img.(*image.RGBA), config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
Example Images
|
||||||
|
==============
|
||||||
|
|
||||||
|
This directory contains example WebP encoded files and PNG source files.
|
||||||
|
|
||||||
|
## Image Credits
|
||||||
|
|
||||||
|
### Photos by Author
|
||||||
|
|
||||||
|
These images are taken by author.
|
||||||
|
These photos are licensed under the Creative Commons Attribution 3.0.
|
||||||
|
You can also use these images under the same as [go-libwebp's license](../LICENSE).
|
||||||
|
|
||||||
|
#### cosmos.png
|
||||||
|
|
||||||
|
The cosmos taken in Nokono-shima (Nokono Island), Fukuoka, JAPAN.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
WebP file is generated by following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cwebp -q 90 cosmos.png -o cosmos.webp
|
||||||
|
Saving file 'cosmos.webp'
|
||||||
|
File: cosmos.png
|
||||||
|
Dimension: 1024 x 768
|
||||||
|
Output: 76954 bytes Y-U-V-All-PSNR 45.87 47.63 48.10 46.44 dB
|
||||||
|
block count: intra4: 2972
|
||||||
|
intra16: 100 (-> 3.26%)
|
||||||
|
skipped block: 1 (0.03%)
|
||||||
|
bytes used: header: 249 (0.3%)
|
||||||
|
mode-partition: 15161 (19.7%)
|
||||||
|
Residuals bytes |segment 1|segment 2|segment 3|segment 4| total
|
||||||
|
macroblocks: | 0%| 11%| 33%| 54%| 3072
|
||||||
|
quantizer: | 12 | 11 | 9 | 8 |
|
||||||
|
filter level: | 4 | 2 | 2 | 4 |
|
||||||
|
```
|
||||||
|
|
||||||
|
#### butterfly.png
|
||||||
|
|
||||||
|
The butterfly taken in Ishigaki-jima (Ishigaki Island) in Okinawa, JAPAN.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
WebP file is generated by following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cwebp -q 90 butterfly.png -o butterfly.webp
|
||||||
|
Saving file 'butterfly.webp'
|
||||||
|
File: butterfly.png
|
||||||
|
Dimension: 1024 x 768
|
||||||
|
Output: 79198 bytes Y-U-V-All-PSNR 45.50 48.71 49.90 46.44 dB
|
||||||
|
block count: intra4: 2775
|
||||||
|
intra16: 297 (-> 9.67%)
|
||||||
|
skipped block: 0 (0.00%)
|
||||||
|
bytes used: header: 383 (0.5%)
|
||||||
|
mode-partition: 13227 (16.7%)
|
||||||
|
Residuals bytes |segment 1|segment 2|segment 3|segment 4| total
|
||||||
|
macroblocks: | 1%| 7%| 14%| 75%| 3072
|
||||||
|
quantizer: | 12 | 12 | 10 | 8 |
|
||||||
|
filter level: | 4 | 3 | 2 | 6 |
|
||||||
|
```
|
||||||
|
|
||||||
|
#### kinkaku.png
|
||||||
|
|
||||||
|
Kinkaku taken in Kyoto, JAPAN.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
WebP file is generated by following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cwebp -q 90 kinkaku.png -o kinkaku.webp
|
||||||
|
Saving file 'kinkaku.webp'
|
||||||
|
File: kinkaku.png
|
||||||
|
Dimension: 1024 x 768
|
||||||
|
Output: 186300 bytes Y-U-V-All-PSNR 43.86 47.25 48.26 44.81 dB
|
||||||
|
block count: intra4: 2775
|
||||||
|
intra16: 297 (-> 9.67%)
|
||||||
|
skipped block: 243 (7.91%)
|
||||||
|
bytes used: header: 425 (0.2%)
|
||||||
|
mode-partition: 16737 (9.0%)
|
||||||
|
Residuals bytes |segment 1|segment 2|segment 3|segment 4| total
|
||||||
|
macroblocks: | 2%| 28%| 40%| 28%| 3072
|
||||||
|
quantizer: | 12 | 11 | 9 | 6 |
|
||||||
|
filter level: | 4 | 2 | 2 | 1 |
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Google WebP Gallery
|
||||||
|
|
||||||
|
These images picked up from [WebP Gallery](https://developers.google.com/speed/webp/gallery) in Google Developers.
|
||||||
|
|
||||||
|
#### yellow-rose-3.png
|
||||||
|
|
||||||
|
"Free Stock Photo in High Resolution - Yellow Rose 3 - Flowers" by Jon Sullivan
|
||||||
|
|
||||||
|
This image is clipped by Google and licensed under [Creative Commons Attribution 3.0](https://creativecommons.org/licenses/by/3.0/). The Source of this image is in the public domain.
|
||||||
|
|
||||||
|
<div style="background:url('./checkerboard.png')">
|
||||||
|
<img title="yellow-rose-3.png" src="yellow-rose-3.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
#### fizyplankton.png
|
||||||
|
|
||||||
|
"baby tux for my user page" by Fizyplankton.
|
||||||
|
|
||||||
|
This image file is in the public domain.
|
||||||
|
|
||||||
|
<div style="background:url('./checkerboard.png')">
|
||||||
|
<img title="fizyplankton.png" src="fizyplankton.png">
|
||||||
|
</div>
|
||||||
|
After Width: | Height: | Size: 997 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 964 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,80 @@
|
|||||||
|
// Package util contains utility code for demosntration of go-libwebp.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetExFilePath returns the path of specified example file.
|
||||||
|
func GetExFilePath(name string) string {
|
||||||
|
return filepath.Join(os.Getenv("GOPATH"), "src/github.com/harukasan/go-libwebp/examples/images", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOutFilePath returns the path of specified out file.
|
||||||
|
func GetOutFilePath(name string) string {
|
||||||
|
return filepath.Join(os.Getenv("GOPATH"), "src/github.com/harukasan/go-libwebp/examples/out", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile opens specified example file
|
||||||
|
func OpenFile(name string) (io io.Reader) {
|
||||||
|
io, err := os.Open(GetExFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads and returns data bytes of specified example file.
|
||||||
|
func ReadFile(name string) (data []byte) {
|
||||||
|
data, err := ioutil.ReadFile(GetExFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFile opens specified example file
|
||||||
|
func CreateFile(name string) (f *os.File) {
|
||||||
|
f, err := os.Create(GetOutFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePNG encodes and writes image into PNG file.
|
||||||
|
func WritePNG(img image.Image, name string) {
|
||||||
|
f, err := os.Create(GetOutFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewWriter(f)
|
||||||
|
defer func() {
|
||||||
|
b.Flush()
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := png.Encode(b, img); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPNG reads and decodes png data into image.Image
|
||||||
|
func ReadPNG(name string) (img image.Image) {
|
||||||
|
io, err := os.Open(GetExFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
img, err = png.Decode(io)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
mode: count
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:41.34,43.2 1 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:46.47,50.2 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:53.65,57.37 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:61.2,69.8 2 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:57.37,59.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:73.83,75.16 2 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:79.2,83.107 3 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:87.2,96.42 6 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:100.2,114.32 14 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:120.2,120.95 1 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:124.2,124.8 1 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:75.16,77.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:83.107,85.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:96.42,98.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:114.32,118.3 3 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:120.95,122.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:128.84,130.16 2 3
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:134.2,138.107 3 3
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:143.2,157.95 9 3
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:161.2,161.8 1 3
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:130.16,132.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:138.107,140.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:157.95,159.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:165.55,166.16 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:184.2,184.33 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:167.2,168.25 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:169.2,170.36 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:171.2,172.36 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:173.2,174.38 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:175.2,176.42 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:177.2,178.32 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:179.2,180.33 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:181.2,182.38 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:188.102,191.57 2 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:196.2,196.29 1 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:199.2,199.31 1 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:202.2,202.54 1 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:209.2,209.56 1 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:214.2,214.24 1 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:217.12,219.8 2 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:191.57,193.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:196.29,198.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:199.31,201.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:202.54,208.3 5 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:209.56,213.3 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:214.24,216.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:223.82,225.29 2 5
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:230.2,230.37 1 4
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:236.2,238.18 3 3
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:225.29,229.3 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/decode.go:230.37,234.3 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:42.101,46.16 4 11
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:49.2,49.10 1 11
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:46.16,48.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:53.69,55.16 2 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:59.2,60.49 2 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:63.12,73.46 8 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:77.2,77.8 1 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:55.16,57.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:60.49,62.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:73.46,75.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:81.68,83.16 2 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:87.2,88.49 2 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:91.12,101.31 10 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:106.2,109.46 3 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:113.2,113.8 1 1
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:83.16,85.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:88.49,90.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:101.31,104.3 2 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:109.46,111.13 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:117.73,119.96 2 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:122.12,125.44 3 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:128.2,128.8 1 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:119.96,121.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/encode.go:125.44,127.3 1 0
|
||||||
|
github.com/harukasan/go-libwebp/webp/yuva_image.go:21.71,25.11 3 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/yuva_image.go:55.2,55.8 1 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/yuva_image.go:26.2,38.4 2 2
|
||||||
|
github.com/harukasan/go-libwebp/webp/yuva_image.go:40.2,52.4 2 0
|
||||||
@ -0,0 +1,242 @@
|
|||||||
|
package webp
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lwebp
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <webp/decode.h>
|
||||||
|
|
||||||
|
static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer);
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecoderOptions specifies decoding options
|
||||||
|
type DecoderOptions struct {
|
||||||
|
BypassFiltering bool
|
||||||
|
NoFancyUpsampling bool
|
||||||
|
Crop image.Rectangle
|
||||||
|
Scale image.Rectangle
|
||||||
|
UseThreads bool
|
||||||
|
DitheringStrength int
|
||||||
|
Flip bool
|
||||||
|
AlphaDitheringStrength int
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitstreamFeatures retrived from data stream.
|
||||||
|
type BitstreamFeatures struct {
|
||||||
|
Width int // Image width in pixels
|
||||||
|
Height int // Image height in pixles
|
||||||
|
HasAlpha bool // True if data stream contains a alpha channel.
|
||||||
|
HasAnimation bool // True if data stream is an animation
|
||||||
|
Format int // Image compression format
|
||||||
|
NoIncrementalDecoding bool // True if incremental decording is not using
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDecoderVersion returns decoder's version number, packed in hexadecimal.
|
||||||
|
// e.g; v0.4.2 is 0x000402
|
||||||
|
func GetDecoderVersion() (v int) {
|
||||||
|
return int(C.WebPGetDecoderVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo retrives width/height from data bytes.
|
||||||
|
func GetInfo(data []byte) (width, height int) {
|
||||||
|
var w, h C.int
|
||||||
|
C.WebPGetInfo((*C.uint8_t)(&data[0]), (C.size_t)(len(data)), &w, &h)
|
||||||
|
return int(w), int(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatures returns features retrived from data stream.
|
||||||
|
func GetFeatures(data []byte) (f *BitstreamFeatures, err error) {
|
||||||
|
var cf C.WebPBitstreamFeatures
|
||||||
|
status := C.WebPGetFeatures((*C.uint8_t)(&data[0]), (C.size_t)(len(data)), &cf)
|
||||||
|
|
||||||
|
if status != C.VP8_STATUS_OK {
|
||||||
|
return nil, fmt.Errorf("WebPGetFeatures returns unexpected status: %s", statusString(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
f = &BitstreamFeatures{
|
||||||
|
Width: int(cf.width), // TODO: use Rectangle instaed?
|
||||||
|
Height: int(cf.height),
|
||||||
|
HasAlpha: cf.has_alpha > 0,
|
||||||
|
HasAnimation: cf.has_animation > 0,
|
||||||
|
Format: int(cf.format),
|
||||||
|
NoIncrementalDecoding: cf.no_incremental_decoding == 1,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeYUVA decodes WebP image into YUV image with alpha channel.
|
||||||
|
func DecodeYUVA(data []byte, options *DecoderOptions) (img *YUVAImage, err error) {
|
||||||
|
config, err := initDecoderConfig(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cDataPtr := (*C.uint8_t)(&data[0])
|
||||||
|
cDataSize := (C.size_t)(len(data))
|
||||||
|
|
||||||
|
// Retrive WebP features from data stream
|
||||||
|
if status := C.WebPGetFeatures(cDataPtr, cDataSize, &config.input); status != C.VP8_STATUS_OK {
|
||||||
|
return nil, fmt.Errorf("Could not get features from the data stream, return %s", statusString(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
outWidth, outHeight := calcOutputSize(config)
|
||||||
|
buf := (*C.WebPYUVABuffer)(unsafe.Pointer(&config.output.u[0]))
|
||||||
|
|
||||||
|
// Set up output configurations
|
||||||
|
colorSpace := YUV420
|
||||||
|
config.output.colorspace = C.MODE_YUV
|
||||||
|
if config.input.has_alpha > 0 {
|
||||||
|
colorSpace = YUV420A
|
||||||
|
config.output.colorspace = C.MODE_YUVA
|
||||||
|
}
|
||||||
|
config.output.is_external_memory = 1
|
||||||
|
|
||||||
|
// Allocate image and fill into buffer
|
||||||
|
img = NewYUVAImage(image.Rect(0, 0, outWidth, outHeight), colorSpace)
|
||||||
|
buf.y = (*C.uint8_t)(&img.Y[0])
|
||||||
|
buf.u = (*C.uint8_t)(&img.Cb[0])
|
||||||
|
buf.v = (*C.uint8_t)(&img.Cr[0])
|
||||||
|
buf.a = nil
|
||||||
|
buf.y_stride = C.int(img.YStride)
|
||||||
|
buf.u_stride = C.int(img.CStride)
|
||||||
|
buf.v_stride = C.int(img.CStride)
|
||||||
|
buf.a_stride = 0
|
||||||
|
buf.y_size = C.size_t(len(img.Y))
|
||||||
|
buf.u_size = C.size_t(len(img.Cb))
|
||||||
|
buf.v_size = C.size_t(len(img.Cr))
|
||||||
|
buf.a_size = 0
|
||||||
|
|
||||||
|
if config.input.has_alpha > 0 {
|
||||||
|
buf.a = (*C.uint8_t)(&img.A[0])
|
||||||
|
buf.a_stride = C.int(img.AStride)
|
||||||
|
buf.a_size = C.size_t(len(img.A))
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := C.WebPDecode(cDataPtr, cDataSize, config); status != C.VP8_STATUS_OK {
|
||||||
|
return nil, fmt.Errorf("Could not decode data stream, return %s", statusString(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRGBA decodes WebP image into RGBA image.
|
||||||
|
func DecodeRGBA(data []byte, options *DecoderOptions) (img *image.RGBA, err error) {
|
||||||
|
config, err := initDecoderConfig(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cDataPtr := (*C.uint8_t)(&data[0])
|
||||||
|
cDataSize := (C.size_t)(len(data))
|
||||||
|
|
||||||
|
// Retrive WebP features
|
||||||
|
if status := C.WebPGetFeatures(cDataPtr, cDataSize, &config.input); status != C.VP8_STATUS_OK {
|
||||||
|
return nil, fmt.Errorf("Could not get features from the data stream, return %s", statusString(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate output image
|
||||||
|
outWidth, outHeight := calcOutputSize(config)
|
||||||
|
img = image.NewRGBA(image.Rect(0, 0, outWidth, outHeight))
|
||||||
|
|
||||||
|
// Set up output configurations
|
||||||
|
config.output.colorspace = C.MODE_RGBA
|
||||||
|
config.output.is_external_memory = 1
|
||||||
|
|
||||||
|
// Allocate WebPRGBABuffer and fill in the pointers to output image
|
||||||
|
buf := (*C.WebPRGBABuffer)(unsafe.Pointer(&config.output.u[0]))
|
||||||
|
buf.rgba = (*C.uint8_t)(&img.Pix[0])
|
||||||
|
buf.stride = C.int(img.Stride)
|
||||||
|
buf.size = (C.size_t)(len(img.Pix))
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
if status := C.WebPDecode(cDataPtr, cDataSize, config); status != C.VP8_STATUS_OK {
|
||||||
|
return nil, fmt.Errorf("Could not decode data stream, return %s", statusString(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sattusString convert the VP8StatsCode to string.
|
||||||
|
func statusString(status C.VP8StatusCode) string {
|
||||||
|
switch status {
|
||||||
|
case C.VP8_STATUS_OK:
|
||||||
|
return "VP8_STATUS_OK"
|
||||||
|
case C.VP8_STATUS_OUT_OF_MEMORY:
|
||||||
|
return "VP8_STATUS_OUT_OF_MEMORY"
|
||||||
|
case C.VP8_STATUS_INVALID_PARAM:
|
||||||
|
return "VP8_STATUS_INVALID_PARAM"
|
||||||
|
case C.VP8_STATUS_BITSTREAM_ERROR:
|
||||||
|
return "VP8_STATUS_BITSTREAM_ERROR"
|
||||||
|
case C.VP8_STATUS_UNSUPPORTED_FEATURE:
|
||||||
|
return "VP8_STATUS_UNSUPPORTED_FEATURE"
|
||||||
|
case C.VP8_STATUS_SUSPENDED:
|
||||||
|
return "VP8_STATUS_SUSPENDED"
|
||||||
|
case C.VP8_STATUS_USER_ABORT:
|
||||||
|
return "VP8_STATUS_USER_ABORT"
|
||||||
|
case C.VP8_STATUS_NOT_ENOUGH_DATA:
|
||||||
|
return "VP8_STATUS_NOT_ENOUGH_DATA"
|
||||||
|
}
|
||||||
|
return "Unexpected Status Code"
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDecoderConfing initializes a decoder configration and sets up the options.
|
||||||
|
func initDecoderConfig(options *DecoderOptions) (config *C.WebPDecoderConfig, err error) {
|
||||||
|
// Initialize decoder config
|
||||||
|
config = &C.WebPDecoderConfig{}
|
||||||
|
if C.WebPInitDecoderConfig(config) == 0 {
|
||||||
|
return nil, errors.New("Could not initialize decoder config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up decoder options
|
||||||
|
if options.BypassFiltering {
|
||||||
|
config.options.bypass_filtering = 1
|
||||||
|
}
|
||||||
|
if options.NoFancyUpsampling {
|
||||||
|
config.options.no_fancy_upsampling = 1
|
||||||
|
}
|
||||||
|
if options.Crop.Max.X > 0 && options.Crop.Max.Y > 0 {
|
||||||
|
config.options.use_cropping = 1
|
||||||
|
config.options.crop_left = C.int(options.Crop.Min.X)
|
||||||
|
config.options.crop_top = C.int(options.Crop.Min.Y)
|
||||||
|
config.options.crop_width = C.int(options.Crop.Dx())
|
||||||
|
config.options.crop_height = C.int(options.Crop.Dy())
|
||||||
|
}
|
||||||
|
if options.Scale.Max.X > 0 && options.Scale.Max.Y > 0 {
|
||||||
|
config.options.use_scaling = 1
|
||||||
|
config.options.scaled_width = C.int(options.Scale.Max.X)
|
||||||
|
config.options.scaled_height = C.int(options.Scale.Max.Y)
|
||||||
|
}
|
||||||
|
if options.UseThreads {
|
||||||
|
config.options.use_threads = 1
|
||||||
|
}
|
||||||
|
config.options.dithering_strength = C.int(options.DitheringStrength)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcOutputSize retrives width and height of output image from the decoder configuration.
|
||||||
|
func calcOutputSize(config *C.WebPDecoderConfig) (width, height int) {
|
||||||
|
options := config.options
|
||||||
|
if options.use_scaling > 0 {
|
||||||
|
width = int(config.options.scaled_width)
|
||||||
|
height = int(config.options.scaled_height)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.options.use_cropping > 0 {
|
||||||
|
width = int(config.options.crop_width)
|
||||||
|
height = int(config.options.crop_height)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
width = int(config.input.width)
|
||||||
|
height = int(config.input.height)
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
package webp
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lwebp
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <webp/encode.h>
|
||||||
|
|
||||||
|
int writeWebP(uint8_t*, size_t, struct WebPPicture*);
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config specifies WebP encoding configuration.
|
||||||
|
type Config struct {
|
||||||
|
Preset Preset // Parameters Preset
|
||||||
|
Lossless bool // True if use lossless encoding
|
||||||
|
Quality float32 // WebP quality factor, 0-100
|
||||||
|
Method int // Quality/Speed trade-off factor, 0=faster / 6=slower-better
|
||||||
|
TargetSize int // Target size of encoded file in bytes
|
||||||
|
TargetPSNR float32 // Target PSNR, takes precedence over TargetSize
|
||||||
|
Segments int // Maximum number of segments to use, 1..4
|
||||||
|
SNSStrength int // Strength of spartial noise shaping, 0..100=maximum
|
||||||
|
FilterStrength int // Strength of filter, 0..100=strength
|
||||||
|
FilterSharpness int // Sharpness of filter, 0..7=sharpness
|
||||||
|
FilterType FilterType // Filtering type
|
||||||
|
Pass int // Number of entropy-analysis passes, 0..100
|
||||||
|
}
|
||||||
|
|
||||||
|
type destinationManager struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
//export writeWebP
|
||||||
|
func writeWebP(data *C.uint8_t, size C.size_t, pic *C.WebPPicture) C.int {
|
||||||
|
mgr := (*destinationManager)(unsafe.Pointer(pic.custom_ptr))
|
||||||
|
bytes := C.GoBytes(unsafe.Pointer(data), C.int(size))
|
||||||
|
_, err := mgr.writer.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return 0 // TODO: can't pass error message
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRGBA encodes and writes image.Image into the writer as WebP.
|
||||||
|
// Now supports image.RGBA or image.NRGBA.
|
||||||
|
func EncodeRGBA(w io.Writer, img image.Image, c Config) (err error) {
|
||||||
|
webpConfig, err := initConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pic C.WebPPicture
|
||||||
|
if C.WebPPictureInit(&pic) == 0 {
|
||||||
|
return errors.New("Could not initialize webp picture")
|
||||||
|
}
|
||||||
|
pic.use_argb = 1
|
||||||
|
|
||||||
|
pic.width = C.int(img.Bounds().Dx())
|
||||||
|
pic.height = C.int(img.Bounds().Dy())
|
||||||
|
|
||||||
|
pic.writer = C.WebPWriterFunction(C.writeWebP)
|
||||||
|
pic.custom_ptr = unsafe.Pointer(&destinationManager{writer: w})
|
||||||
|
|
||||||
|
switch p := img.(type) {
|
||||||
|
case *image.RGBA:
|
||||||
|
C.WebPPictureImportRGBA(&pic, (*C.uint8_t)(&p.Pix[0]), C.int(p.Stride))
|
||||||
|
case *image.NRGBA:
|
||||||
|
C.WebPPictureImportRGBA(&pic, (*C.uint8_t)(&p.Pix[0]), C.int(p.Stride))
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported image type")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer C.WebPPictureFree(&pic)
|
||||||
|
|
||||||
|
if C.WebPEncode(webpConfig, &pic) == 0 {
|
||||||
|
return fmt.Errorf("Encoding error: %d", pic.error_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeYUVA encodes and writes YUVA Image data into the writer as WebP.
|
||||||
|
func EncodeYUVA(w io.Writer, img *YUVAImage, c Config) (err error) {
|
||||||
|
webpConfig, err := initConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pic C.WebPPicture
|
||||||
|
if C.WebPPictureInit(&pic) == 0 {
|
||||||
|
return errors.New("Could not initialize webp picture")
|
||||||
|
}
|
||||||
|
pic.use_argb = 0
|
||||||
|
pic.colorspace = C.WebPEncCSP(img.ColorSpace)
|
||||||
|
pic.width = C.int(img.Rect.Dx())
|
||||||
|
pic.height = C.int(img.Rect.Dy())
|
||||||
|
pic.y = (*C.uint8_t)(&img.Y[0])
|
||||||
|
pic.u = (*C.uint8_t)(&img.Cb[0])
|
||||||
|
pic.v = (*C.uint8_t)(&img.Cr[0])
|
||||||
|
pic.y_stride = C.int(img.YStride)
|
||||||
|
pic.uv_stride = C.int(img.CStride)
|
||||||
|
|
||||||
|
if img.ColorSpace == YUV420A {
|
||||||
|
pic.a = (*C.uint8_t)(&img.A[0])
|
||||||
|
pic.a_stride = C.int(img.AStride)
|
||||||
|
}
|
||||||
|
|
||||||
|
pic.writer = C.WebPWriterFunction(C.writeWebP)
|
||||||
|
pic.custom_ptr = unsafe.Pointer(&destinationManager{writer: w})
|
||||||
|
|
||||||
|
if C.WebPEncode(webpConfig, &pic) == 0 {
|
||||||
|
return fmt.Errorf("Encoding error: %d", pic.error_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// initConfig initializes C.WebPConfig with encoding parameters.
|
||||||
|
func initConfig(c Config) (config *C.WebPConfig, err error) {
|
||||||
|
config = &C.WebPConfig{}
|
||||||
|
if C.WebPConfigPreset(config, C.WebPPreset(c.Preset), C.float(c.Quality)) == 0 {
|
||||||
|
return nil, errors.New("Could not initialize configuration with preset")
|
||||||
|
}
|
||||||
|
config.target_size = C.int(c.TargetSize)
|
||||||
|
config.target_PSNR = C.float(c.TargetPSNR)
|
||||||
|
|
||||||
|
if C.WebPValidateConfig(config) == 0 {
|
||||||
|
return nil, errors.New("Invalid configuration")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package webp
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lwebp
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <webp/encode.h>
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// ColorSpace represents encoding color space in WebP
|
||||||
|
type ColorSpace int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// YUV420 specifies YUV4:2:0
|
||||||
|
YUV420 ColorSpace = C.WEBP_YUV420
|
||||||
|
// YUV420A specifies YUV4:2:0 with alpha channel
|
||||||
|
YUV420A ColorSpace = C.WEBP_YUV420A
|
||||||
|
)
|
||||||
|
|
||||||
|
// Preset corresponds to C.WebPPreset
|
||||||
|
type Preset int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PresetDefault corresponds to WEBP_PRESET_DEFAULT, for default preset.
|
||||||
|
PresetDefault Preset = C.WEBP_PRESET_DEFAULT
|
||||||
|
// PresetPicture corresponds to WEBP_PRESET_PICTURE, for digital picture, like portrait, inner shot
|
||||||
|
PresetPicture Preset = C.WEBP_PRESET_PICTURE
|
||||||
|
// PresetPhoto corresponds to WEBP_PRESET_PHOTO, for outdoor photograph, with natural lighting
|
||||||
|
PresetPhoto Preset = C.WEBP_PRESET_PHOTO
|
||||||
|
// PresetDrawing corresponds to WEBP_PRESET_DRAWING, for hand or line drawing, with high-contrast details
|
||||||
|
PresetDrawing Preset = C.WEBP_PRESET_DRAWING
|
||||||
|
// PresetIcon corresponds to WEBP_PRESET_ICON, for small-sized colorful images
|
||||||
|
PresetIcon Preset = C.WEBP_PRESET_ICON
|
||||||
|
// PresetText corresponds to WEBP_PRESET_TEXT, for text-like
|
||||||
|
PresetText Preset = C.WEBP_PRESET_TEXT
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterType corresponds to filter types in compression parameters.
|
||||||
|
type FilterType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SimpleFilter (=0, default)
|
||||||
|
SimpleFilter FilterType = iota
|
||||||
|
// StrongFilter (=1)
|
||||||
|
StrongFilter
|
||||||
|
)
|
||||||
@ -0,0 +1,236 @@
|
|||||||
|
package webp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/harukasan/go-libwebp/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// Utitlities of input/output example images
|
||||||
|
//
|
||||||
|
|
||||||
|
// GetExFilePath returns the path of specified example file.
|
||||||
|
func GetExFilePath(name string) string {
|
||||||
|
return filepath.Join(os.Getenv("GOPATH"), "src/github.com/harukasan/go-libwebp/examples/images", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenExFile opens specified example file
|
||||||
|
func OpenExFile(name string) (io io.Reader) {
|
||||||
|
io, err := os.Open(GetExFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadExFile reads and returns data bytes of specified example file.
|
||||||
|
func ReadExFile(name string) (data []byte) {
|
||||||
|
data, err := ioutil.ReadFile(GetExFilePath(name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateExOutFile creates output file into example/out directory.
|
||||||
|
func CreateExOutFile(name string) (file *os.File) {
|
||||||
|
// Make output directory
|
||||||
|
dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/harukasan/go-libwebp/examples/out")
|
||||||
|
if err := os.Mkdir(dir, 0755); err != nil && os.IsNotExist(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create example output file
|
||||||
|
file, err := os.Create(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Decode
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test Get Decoder Version
|
||||||
|
func TestGetDecoderVersion(t *testing.T) {
|
||||||
|
v := webp.GetDecoderVersion()
|
||||||
|
if v < 0 {
|
||||||
|
t.Errorf("GetDecoderVersion should returns positive version number, got %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInfo(t *testing.T) {
|
||||||
|
data := ReadExFile("cosmos.webp")
|
||||||
|
width, height := webp.GetInfo(data)
|
||||||
|
|
||||||
|
if width != 1024 {
|
||||||
|
t.Errorf("Expected width: %d, but got %d", 1024, width)
|
||||||
|
}
|
||||||
|
if height != 768 {
|
||||||
|
t.Errorf("Expected height: %d, but got %d", 768, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFeatures(t *testing.T) {
|
||||||
|
data := ReadExFile("cosmos.webp")
|
||||||
|
f, err := webp.GetFeatures(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got := f.Width; got != 1024 {
|
||||||
|
t.Errorf("Expected Width: %v, but got %v", 1024, got)
|
||||||
|
}
|
||||||
|
if got := f.Height; got != 768 {
|
||||||
|
t.Errorf("Expected Width: %v, but got %v", 768, got)
|
||||||
|
}
|
||||||
|
if got := f.HasAlpha; got != false {
|
||||||
|
t.Errorf("Expected HasAlpha: %v, but got %v", false, got)
|
||||||
|
}
|
||||||
|
if got := f.HasAnimation; got != false {
|
||||||
|
t.Errorf("Expected HasAlpha: %v, but got %v", false, got)
|
||||||
|
}
|
||||||
|
if got := f.Format; got != 1 {
|
||||||
|
t.Errorf("Expected Format: %v, but got %v", 1, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeYUV(t *testing.T) {
|
||||||
|
files := []string{
|
||||||
|
"cosmos.webp",
|
||||||
|
"butterfly.webp",
|
||||||
|
"kinkaku.webp",
|
||||||
|
"yellow-rose-3.webp",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
data := ReadExFile(file)
|
||||||
|
options := &webp.DecoderOptions{}
|
||||||
|
|
||||||
|
_, err := webp.DecodeYUVA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRGBA(t *testing.T) {
|
||||||
|
files := []string{
|
||||||
|
"cosmos.webp",
|
||||||
|
"butterfly.webp",
|
||||||
|
"kinkaku.webp",
|
||||||
|
"yellow-rose-3.webp",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
data := ReadExFile(file)
|
||||||
|
options := &webp.DecoderOptions{}
|
||||||
|
|
||||||
|
_, err := webp.DecodeRGBA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRGBAWithCropping(t *testing.T) {
|
||||||
|
data := ReadExFile("cosmos.webp")
|
||||||
|
crop := image.Rect(100, 100, 300, 200)
|
||||||
|
|
||||||
|
options := &webp.DecoderOptions{
|
||||||
|
Crop: crop,
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := webp.DecodeRGBA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if img.Rect.Dx() != crop.Dx() || img.Rect.Dy() != crop.Dy() {
|
||||||
|
t.Errorf("Decoded image should cropped to %v, but got %v", crop, img.Rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRGBAWithScaling(t *testing.T) {
|
||||||
|
data := ReadExFile("cosmos.webp")
|
||||||
|
scale := image.Rect(0, 0, 640, 480)
|
||||||
|
|
||||||
|
options := &webp.DecoderOptions{
|
||||||
|
Scale: scale,
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := webp.DecodeRGBA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if img.Rect.Dx() != scale.Dx() || img.Rect.Dy() != scale.Dy() {
|
||||||
|
t.Errorf("Decoded image should scaled to %v, but got %v", scale, img.Rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Encoding
|
||||||
|
//
|
||||||
|
|
||||||
|
func TestEncodeRGBA(t *testing.T) {
|
||||||
|
img, _ := png.Decode(OpenExFile("yellow-rose-3.png"))
|
||||||
|
|
||||||
|
config := webp.Config{
|
||||||
|
Preset: webp.PresetDefault,
|
||||||
|
Quality: 100,
|
||||||
|
Method: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
f := CreateExOutFile("TestEncodeRGBA.webp")
|
||||||
|
w := bufio.NewWriter(f)
|
||||||
|
defer func() {
|
||||||
|
w.Flush()
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := webp.EncodeRGBA(w, img, config); err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeYUVA(t *testing.T) {
|
||||||
|
data := ReadExFile("cosmos.webp")
|
||||||
|
options := &webp.DecoderOptions{}
|
||||||
|
|
||||||
|
img, err := webp.DecodeYUVA(data, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got Error: %v in decoding", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f := CreateExOutFile("TestEncodeYUVA.webp")
|
||||||
|
w := bufio.NewWriter(f)
|
||||||
|
defer func() {
|
||||||
|
w.Flush()
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := webp.Config{
|
||||||
|
Preset: webp.PresetDefault,
|
||||||
|
Quality: 100,
|
||||||
|
Method: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := webp.EncodeYUVA(w, img, config); err != nil {
|
||||||
|
t.Errorf("Got Error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package webp
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
|
// YUVAImage represents a image of YUV colors with alpha channel image.
|
||||||
|
//
|
||||||
|
// We can not insert WebP planer image into image.YCbCr,
|
||||||
|
// because image.YCbCr is not compatible with ITU-R BT.601, but JFIF/JPEG one.
|
||||||
|
//
|
||||||
|
// See: http://en.wikipedia.org/wiki/YCbCr
|
||||||
|
type YUVAImage struct {
|
||||||
|
Y, Cb, Cr, A []uint8
|
||||||
|
YStride int
|
||||||
|
CStride int
|
||||||
|
AStride int
|
||||||
|
ColorSpace ColorSpace
|
||||||
|
Rect image.Rectangle
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYUVAImage creates and allocates image buffer.
|
||||||
|
func NewYUVAImage(r image.Rectangle, c ColorSpace) (image *YUVAImage) {
|
||||||
|
yw, yh := r.Dx(), r.Dx()
|
||||||
|
cw, ch := ((r.Max.X+1)/2 - r.Min.X/2), ((r.Max.Y+1)/2 - r.Min.Y/2)
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case YUV420:
|
||||||
|
b := make([]byte, yw*yh+2*cw*ch)
|
||||||
|
image = &YUVAImage{
|
||||||
|
Y: b[:yw*yh],
|
||||||
|
Cb: b[yw*yh+0*cw*ch : yw*yh+1*cw*ch],
|
||||||
|
Cr: b[yw*yh+1*cw*ch : yw*yh+2*cw*ch],
|
||||||
|
A: nil,
|
||||||
|
YStride: yw,
|
||||||
|
CStride: cw,
|
||||||
|
AStride: 0,
|
||||||
|
ColorSpace: c,
|
||||||
|
Rect: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
case YUV420A:
|
||||||
|
b := make([]byte, 2*yw*yh+2*cw*ch)
|
||||||
|
image = &YUVAImage{
|
||||||
|
Y: b[:yw*yh],
|
||||||
|
Cb: b[yw*yh+0*cw*ch : yw*yh+1*cw*ch],
|
||||||
|
Cr: b[yw*yh+1*cw*ch : yw*yh+2*cw*ch],
|
||||||
|
A: b[yw*yh+2*cw*ch:],
|
||||||
|
YStride: yw,
|
||||||
|
CStride: cw,
|
||||||
|
AStride: yw,
|
||||||
|
ColorSpace: c,
|
||||||
|
Rect: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||