mirror of
https://github.com/talgo-cloud/talgo-libwebp.git
synced 2026-03-13 09:20:35 -07:00
initial commit
This commit is contained in:
commit
5af6d93b3b
26 changed files with 1165 additions and 0 deletions
80
webp/cover.out
Normal file
80
webp/cover.out
Normal file
|
|
@ -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
|
||||
242
webp/decode.go
Normal file
242
webp/decode.go
Normal file
|
|
@ -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
|
||||
}
|
||||
139
webp/encode.go
Normal file
139
webp/encode.go
Normal file
|
|
@ -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
|
||||
}
|
||||
48
webp/webp.go
Normal file
48
webp/webp.go
Normal file
|
|
@ -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
|
||||
)
|
||||
236
webp/webp_test.go
Normal file
236
webp/webp_test.go
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
56
webp/yuva_image.go
Normal file
56
webp/yuva_image.go
Normal file
|
|
@ -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…
Add table
Add a link
Reference in a new issue