Skip to content

Commit

Permalink
New Webp (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
michalderdak authored Oct 14, 2024
1 parent c3e17e2 commit b529a02
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 53 deletions.
85 changes: 76 additions & 9 deletions coding.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ package imagecoding

import (
"errors"
"image"
"image/color"
"math"

"github.com/harukasan/go-libwebp/webp"
)

// RGBImage is a good idea, so let's borrow it and make it our own
type RGBImage = webp.RGBImage
type RGB = webp.RGB

var RGBModel = webp.RGBModel

type ScaleFunc func(pageWidth, pageHeight int) (imgWidth, imgHeight int, scaleFactor float64)

const (
Expand Down Expand Up @@ -59,4 +53,77 @@ const (
Heif ImgFormat = "heif"
)

var EmptyInputError = errors.New("empty input data")
var ErrEmptyInput = errors.New("empty input data")

// RGBImage represent image data which has RGB colors.
// RGBImage is compatible with image.RGBA, but does not have alpha channel to reduce using memory.
type RGBImage struct {
// Pix holds the image's stream, in R, G, B order.
Pix []uint8
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
Rect image.Rectangle
}

// NewRGBImage allocates and returns RGB image
func NewRGBImage(r image.Rectangle) *RGBImage {
w, h := r.Dx(), r.Dy()
return &RGBImage{Pix: make([]uint8, 3*w*h), Stride: 3 * w, Rect: r}
}

// ColorModel returns RGB color model.
func (p *RGBImage) ColorModel() color.Model {
return RGBModel
}

// Bounds implements image.Image.At
func (p *RGBImage) Bounds() image.Rectangle {
return p.Rect
}

// At implements image.Image.At
func (p *RGBImage) At(x, y int) color.Color {
return p.RGBAAt(x, y)
}

// RGBAAt returns the color of the pixel at (x, y) as RGBA.
func (p *RGBImage) RGBAAt(x, y int) color.RGBA {
if !(image.Point{x, y}.In(p.Rect)) {
return color.RGBA{}
}
i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3
return color.RGBA{p.Pix[i+0], p.Pix[i+1], p.Pix[i+2], 0xFF}
}

// RGBModel is RGB color model instance
var RGBModel = color.ModelFunc(rgbModel)

func rgbModel(c color.Color) color.Color {
if _, ok := c.(RGB); ok {
return c
}
r, g, b, _ := c.RGBA()
return RGB{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8)}
}

// RGB color
type RGB struct {
R, G, B uint8
}

// RGBA implements Color.RGBA
func (c RGB) RGBA() (r, g, b, a uint32) {
r = uint32(c.R)
r |= r << 8
g = uint32(c.G)
g |= g << 8
b = uint32(c.B)
b |= b << 8
a = uint32(0xFFFF)
return
}

// Make sure RGBImage implements image.Image.
// See https://golang.org/doc/effective_go.html#blank_implements.
var _ image.Image = new(RGBImage)
2 changes: 1 addition & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// For JPEGs it uses jpeg-turbos internal function for compatibility
func DecodeConfig(content []byte) (image.Config, string, error) {
if len(content) == 0 {
return image.Config{}, "", EmptyInputError
return image.Config{}, "", ErrEmptyInput
}
// Look at the magic bytes to determine the file type
kind, err := filetype.Match(content)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0
github.com/disintegration/imaging v1.6.2
github.com/h2non/filetype v1.1.3
github.com/harukasan/go-libwebp v0.0.0-20220408054828-61eedf90d768
github.com/kolesa-team/go-webp v1.0.4
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/stretchr/testify v1.8.4
github.com/strukturag/libheif v1.11.0
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0 h1:8cjsGKoi/1QBb0V2ps3iBra9c4o+qY/NaJ15NsdjmQ4=
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0/go.mod h1:PSWPVD+KeWK3XVt0i/AahAMRw38OZ1k1vJpJLuvIY1w=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/harukasan/go-libwebp v0.0.0-20220408054828-61eedf90d768 h1:6kHMeZ8a/fyiHlsR4fwBWaVh3KSBgsMrupXAA5pChfc=
github.com/harukasan/go-libwebp v0.0.0-20220408054828-61eedf90d768/go.mod h1:ldE44ycRKJi6dVHIWnbUlEJqHQUhK5gJ4TKIfAwFbCg=
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/strukturag/libheif v1.11.0 h1:HaWu5re98INSXNq7C8o5AwLcv2qD8+U7a+jVCpGWemI=
Expand All @@ -24,10 +27,14 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
6 changes: 3 additions & 3 deletions heif.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func ConfigHeif(data []byte) (image.Config, string, error) {
if len(data) == 0 {
return image.Config{}, string(Heif), EmptyInputError
return image.Config{}, string(Heif), ErrEmptyInput
}
ctx, err := heif.NewContext()
if err != nil {
Expand All @@ -38,7 +38,7 @@ func ConfigHeif(data []byte) (image.Config, string, error) {

func DecodeHeif(data []byte) (image.Image, error) {
if len(data) == 0 {
return nil, EmptyInputError
return nil, ErrEmptyInput
}
ctx, err := heif.NewContext()
if err != nil {
Expand Down Expand Up @@ -66,7 +66,7 @@ func DecodeHeif(data []byte) (image.Image, error) {

func TransformHeif(data []byte, grayscale bool, scale ScaleFunc) (out image.Image, width, height int, scaleFactor float64, err error) {
if len(data) == 0 {
return nil, 0, 0, 0, EmptyInputError
return nil, 0, 0, 0, ErrEmptyInput
}
ctx, err := heif.NewContext()
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions jpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func getTransformOperation(orient Orientation) []TurboJpegOperation {
// It returns a buffer with JPEG encoding
func ReOrientJpeg(file []byte, orient Orientation) ([]byte, error) {
if len(file) == 0 {
return nil, EmptyInputError
return nil, ErrEmptyInput
}
// Init a transform
tjHandle := C.tjInitTransform()
Expand Down Expand Up @@ -163,7 +163,7 @@ func ReOrientJpeg(file []byte, orient Orientation) ([]byte, error) {

func ConfigJpeg(data []byte) (image.Config, string, error) {
if len(data) == 0 {
return image.Config{}, string(Jpeg), EmptyInputError
return image.Config{}, string(Jpeg), ErrEmptyInput
}

// Init Turbo-JPEG Decompression
Expand Down Expand Up @@ -211,7 +211,7 @@ func ConfigJpeg(data []byte) (image.Config, string, error) {
// This will use libjpeg-turbo to do it as efficiently as possible, utilizing DCT factors for fast scaling
func TransformJpeg(data []byte, grayscale bool, scale ScaleFunc) (out image.Image, width, height int, scaleFactor float64, err error) {
if len(data) == 0 {
return nil, 0, 0, -1, EmptyInputError
return nil, 0, 0, -1, ErrEmptyInput
}

// Init Turbo-JPEG Decompression
Expand Down
2 changes: 1 addition & 1 deletion transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// Transform scales, colormaps and orients an image according to input param
func Transform(data []byte, grayscale bool, scale ScaleFunc) (out image.Image, width, height int, scaleFactor float64, err error) {
if len(data) == 0 {
return nil, 0, 0, 0, EmptyInputError
return nil, 0, 0, 0, ErrEmptyInput
}
// Look at the magic bytes to determine the file type
kind, err := filetype.Match(data)
Expand Down
2 changes: 1 addition & 1 deletion transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestTransformEmpty(t *testing.T) {
empty := []byte{}
_, _, _, _, err := Transform(empty, true, DefaultScale)
if assert.Error(t, err) {
assert.Equal(t, EmptyInputError, err)
assert.Equal(t, ErrEmptyInput, err)
}
}

Expand Down
38 changes: 6 additions & 32 deletions webp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,21 @@ package imagecoding
import (
"bufio"
"bytes"
"errors"
"image"

"github.com/harukasan/go-libwebp/webp"
"github.com/kolesa-team/go-webp/encoder"
newwebp "github.com/kolesa-team/go-webp/webp"
)

func EncodeWebP(buf *bytes.Buffer, img image.Image) ([]byte, error) {
// Efficiency level between 0 (fastest, lowest compression) and 9 (slower, best compression)
// At some point maybe we want to be able to configure this, for now, gotta go fast!
config, configErr := webp.ConfigLosslessPreset(0)

// Graph is the closest match to what we're doing
config.SetImageHint(webp.HintGraph)

// Set a bunch of lossy compression settings, or it won't run
// These are based on the "text" presets
config.SetSegments(2)
config.SetSNSStrength(0)
config.SetPass(1)
config.SetFilterType(webp.StrongFilter)
config.SetFilterStrength(0)
config.SetPreprocessing(webp.PreprocessingNone)
config.SetLossless(true)

if configErr != nil {
return nil, configErr
options, err := encoder.NewLosslessEncoderOptions(encoder.PresetDefault, 0)
if err != nil {
return nil, err
}

pageWriter := bufio.NewWriter(buf)
var err error

switch v := img.(type) {
case *image.Gray:
err = webp.EncodeGray(pageWriter, v, config)
case *webp.RGBImage:
err = webp.EncodeRGBA(pageWriter, img, config)
case *image.RGBA:
err = webp.EncodeRGBA(pageWriter, img, config)
default:
return nil, errors.New("unsupported image type")
}
err = newwebp.Encode(pageWriter, img, options)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit b529a02

Please sign in to comment.