diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4d1fcbf2..dd64ff49 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: @@ -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 @@ -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 diff --git a/convert.go b/decode.go similarity index 65% rename from convert.go rename to decode.go index f4fae83f..00145782 100644 --- a/convert.go +++ b/decode.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Yamagishi Kazutoshi +// Copyright (c) 2021 Yamagishi Kazutoshi // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -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 } @@ -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 -} diff --git a/convert_test.go b/encode.go similarity index 56% rename from convert_test.go rename to encode.go index b3088cba..0f9ee095 100644 --- a/convert_test.go +++ b/encode.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Yamagishi Kazutoshi +// Copyright (c) 2021 Yamagishi Kazutoshi // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -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") } diff --git a/go.mod b/go.mod index 18c85981..5e950c0a 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index b88425d2..7686e601 100644 --- a/go.sum +++ b/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= diff --git a/transport.go b/transport.go index 6cf4d222..c93a3fb2 100644 --- a/transport.go +++ b/transport.go @@ -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 }