Skip to content

Commit

Permalink
feat(avif): add support avif (#372)
Browse files Browse the repository at this point in the history
  • Loading branch information
ykzts committed Mar 2, 2021
1 parent dbe1e5a commit f2721d9
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 119 deletions.
13 changes: 6 additions & 7 deletions .github/workflows/go.yml
Expand Up @@ -12,7 +12,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04

strategy:
matrix:
Expand All @@ -35,7 +35,7 @@ jobs:
GO111MODULE: on
run: |
sudo apt-get update
sudo apt-get install -y libwebp-dev
sudo apt-get install -y libaom-dev libwebp-dev
go mod download
- name: Test
Expand All @@ -49,8 +49,7 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}

- name: GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --snapshot
- name: Build
env:
GO111MODULE: on
run: make
51 changes: 6 additions & 45 deletions convert.go → decode.go
@@ -1,4 +1,4 @@
// Copyright (c) 2017 Yamagishi Kazutoshi <ykzts@desire.sh>
// Copyright (c) 2021 Yamagishi Kazutoshi <ykzts@desire.sh>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,20 +21,19 @@
package manael // import "manael.org/x/manael"

import (
"bytes"
"errors"
"image"
"io"

// register jpeg
_ "image/jpeg"
// register png
_ "image/png"
"io"

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

func decode(src io.Reader) (image.Image, error) {
img, _, err := image.Decode(src)
// Decode returns an image.Image.
func Decode(r io.Reader) (image.Image, error) {
img, _, err := image.Decode(r)
if err != nil {
return nil, err
}
Expand All @@ -56,41 +55,3 @@ func decode(src io.Reader) (image.Image, error) {

return nil, errors.New("Not supported image format")
}

func encode(src image.Image) (*bytes.Buffer, error) {
config, err := webp.ConfigPreset(webp.PresetDefault, 90)
if err != nil {
return nil, err
}

buf := bytes.NewBuffer(nil)
switch img := src.(type) {
case *image.Gray:
err = webp.EncodeGray(buf, img, config)
if err != nil {
return nil, err
}
case *image.RGBA, *image.NRGBA:
err = webp.EncodeRGBA(buf, img, config)
if err != nil {
return nil, err
}
}

return buf, nil
}

// Convert returns a image buffer converted to webp
func Convert(src io.Reader) (*bytes.Buffer, error) {
img, err := decode(src)
if err != nil {
return nil, err
}

buf, err := encode(img)
if err != nil {
return nil, err
}

return buf, nil
}
70 changes: 36 additions & 34 deletions convert_test.go → encode.go
@@ -1,4 +1,4 @@
// Copyright (c) 2018 Yamagishi Kazutoshi <ykzts@desire.sh>
// Copyright (c) 2021 Yamagishi Kazutoshi <ykzts@desire.sh>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,51 +18,53 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package manael_test // import "manael.org/x/manael"
package manael // import "manael.org/x/manael"

import (
"os"
"testing"
"errors"
"image"
"io"

"manael.org/x/manael"
"manael.org/x/manael/internal/testutil"
"github.com/Kagami/go-avif"
"github.com/harukasan/go-libwebp/webp"
)

var convertTests = []struct {
name string
format string
}{
{
"testdata/logo.png",
"webp",
},
{
"testdata/gray.png",
"webp",
},
{
"testdata/photo.jpeg",
"webp",
},
}
// Encode writes the Image m to w in the format specified by Content-Type t.
func Encode(w io.Writer, m image.Image, t string) error {
switch t {
case "image/avif":
opts := avif.Options{
Quality: 20,
Speed: 8,
}

func TestConvert(t *testing.T) {
for _, tc := range convertTests {
f, err := os.Open(tc.name)
err := avif.Encode(w, m, &opts)
if err != nil {
t.Fatal(err)
return err
}
defer f.Close()

img, err := manael.Convert(f)
return nil
case "image/webp":
c, err := webp.ConfigPreset(webp.PresetDefault, 90)
if err != nil {
t.Fatal(err)
return err
}

format := testutil.DetectFormat(img)

if got, want := format, tc.format; got != want {
t.Errorf("Image format is %s, want %s", got, want)
switch img := m.(type) {
case *image.Gray:
err = webp.EncodeGray(w, img, c)
if err != nil {
return err
}
case *image.RGBA, *image.NRGBA:
err = webp.EncodeRGBA(w, img, c)
if err != nil {
return err
}
}

return nil
}

return errors.New("Not supported image type")
}
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -3,6 +3,7 @@ module manael.org/x/manael
go 1.12

require (
github.com/Kagami/go-avif v0.1.0
github.com/gorilla/handlers v1.5.1
github.com/harukasan/go-libwebp v0.0.0-20190703060927-68562c9c99af
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
Expand Down
2 changes: 2 additions & 0 deletions go.sum
@@ -1,3 +1,5 @@
github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w=
github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
Expand Down
93 changes: 60 additions & 33 deletions transport.go
Expand Up @@ -71,77 +71,104 @@ func (t *Transport) makeRequest(r *http.Request) (*http.Request, error) {
return r2, nil
}

func shouldEncodeToWebP(resp *http.Response) bool {
if s := resp.Header.Get("Cache-Control"); s != "" {
func scanAcceptHeader(r *http.Request) string {
accepts := r.Header.Get("Accept")

for _, v := range strings.Split(accepts, ",") {
t := strings.TrimSpace(v)

if strings.HasPrefix(t, "image/avif") {
return "image/avif"
} else if strings.HasPrefix(t, "image/webp") {
return "image/webp"
}
}

return "*/*"
}

func check(w *http.Response, r *http.Request) string {
if r.Method != "GET" {
return "*/*"
}

if w.StatusCode != http.StatusOK && w.StatusCode != http.StatusNotModified {
return "*/*"
}

if s := w.Header.Get("Cache-Control"); s != "" {
for _, v := range strings.Split(s, ",") {
if strings.TrimSpace(v) == "no-transform" {
return false
return "*/*"
}
}
}

if !(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotModified) {
return false
t := w.Header.Get("Content-Type")

if t != "image/jpeg" && t != "image/png" {
return "*/*"
}

contentType := resp.Header.Get("Content-Type")
return contentType == "image/jpeg" || contentType == "image/png"
return scanAcceptHeader(r)
}

func canDecodeWebP(r *http.Request) bool {
accepts := r.Header.Get("Accept")
func convert(src io.Reader, t string) (*bytes.Buffer, error) {
img, err := Decode(src)
if err != nil {
return nil, err
}

for _, v := range strings.Split(accepts, ",") {
t := strings.TrimSpace(v)
if strings.HasPrefix(t, "image/webp") {
return true
}
buf := bytes.NewBuffer(nil)

err = Encode(buf, img, t)
if err != nil {
return nil, err
}

return false
return buf, nil
}

// RoundTrip responds to an converted image.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req2, err := t.makeRequest(req)
r, err := t.makeRequest(req)
if err != nil {
return nil, err
}

resp, err := t.Base.RoundTrip(req2)
w, err := t.Base.RoundTrip(r)
if err != nil {
return nil, err
}

if !(shouldEncodeToWebP(resp) && canDecodeWebP(req2)) {
return resp, nil
typ := check(w, r)
if typ == "*/*" {
return w, nil
}

defer resp.Body.Close()

var body io.Reader = resp.Body
defer w.Body.Close()

p := bytes.NewBuffer(nil)
r := io.TeeReader(body, p)
b := io.TeeReader(w.Body, p)

buf, err := Convert(r)
buf, err := convert(b, typ)
if err != nil {
body = io.MultiReader(p, body)
body := io.MultiReader(p, w.Body)

resp.Body = ioutil.NopCloser(body)
w.Body = ioutil.NopCloser(body)
log.Printf("error: %v\n", err)

return resp, nil
return w, nil
}

resp.Body = ioutil.NopCloser(buf)
w.Body = ioutil.NopCloser(buf)

resp.Header.Set("Content-Type", "image/webp")
resp.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
w.Header.Set("Content-Type", typ)
w.Header.Set("Content-Length", strconv.Itoa(buf.Len()))

if resp.Header.Get("Accept-Ranges") != "" {
resp.Header.Del("Accept-Ranges")
if w.Header.Get("Accept-Ranges") != "" {
w.Header.Del("Accept-Ranges")
}

return resp, nil
return w, nil
}

1 comment on commit f2721d9

@vercel
Copy link

@vercel vercel bot commented on f2721d9 Mar 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.