@ -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
|
||||
}
|
||||