initial commit

master
harukasan 11 years ago
commit 5af6d93b3b

3
.gitignore vendored

@ -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.
![cosmos.png](cosmos.png)
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.
![butterfly.png](butterfly.png)
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.
![kinkaku.png](kinkaku.png)
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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

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
}
Loading…
Cancel
Save