From 3b1399390e4dd6b5077091bf42d9b67eb41257d6 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 12 Jun 2024 16:08:12 -0300 Subject: [PATCH 1/6] feat!: refactor the api endpoint to follow and fix #9 --- api/image.go | 193 +++++++++++------------------------------ components/image.templ | 10 +-- internals/image.go | 132 ++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 147 deletions(-) create mode 100644 internals/image.go diff --git a/api/image.go b/api/image.go index 48f6378..58a09a6 100644 --- a/api/image.go +++ b/api/image.go @@ -1,185 +1,94 @@ package api import ( - "bytes" - "fmt" - "image" - "image/gif" - "image/jpeg" - "image/png" + "errors" "io" - "math" - "mime" "net/http" "net/url" "strconv" - - "github.com/chai2010/webp" - "github.com/disintegration/imaging" + "www/internals" ) -func Scale(i image.Image, s float64) image.Image { - r := i.Bounds() - w, h := float64(r.Max.X), float64(r.Max.Y) - - var nw, nh int - if s < 0 { - s = s * -1 - nw, nh = int(math.Round(w/s)), int(math.Round(h/s)) - } else if s > 0 { - s = s * -1 - nw, nh = int(math.Round(w*s)), int(math.Round(h*s)) - } else { - nw, nh = int(w), int(h) +func errorHelper(w http.ResponseWriter) func(msg string, err error, status int) bool { + return func(msg string, err error, status int) bool { + if err != nil { + w.WriteHeader(status) + _, err = w.Write([]byte(msg + "\n Error: " + err.Error())) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte("Error trying to return error code (somehow):\n" + err.Error())) + } + return true + } + return false } - - return imaging.Resize(i, nw, nh, imaging.CatmullRom) } func Image(w http.ResponseWriter, r *http.Request) { + error := errorHelper(w) + params, err := url.ParseQuery(r.URL.RawQuery) - - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) + if error("Error trying to parse query parameters", err, http.StatusInternalServerError) { return } - var u *url.URL if _, some := params["url"]; !some { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"url\" parameter missing")) + error("\"url\" parameter missing", errors.New("Missing argument"), http.StatusBadRequest) + return + } + u, err := url.Parse(params.Get("url")) + if error("\"url\" is not a valid URL string", err, http.StatusBadRequest) { return - } else { - u, err = url.Parse(params.Get("url")) - - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - - if u.Hostname() == "" { - if r.URL.Scheme == "" { - u.Scheme = "https" - } else { - u.Scheme = r.URL.Scheme - } - u.Host = r.Host - } } - var scale, width, height int - if _, some := params["scale"]; !some { - if _, some := params["width"]; !some { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"width\" parameter missing")) - return + if u.Hostname() == "" { + if r.URL.Scheme == "" { + u.Scheme = "https" } else { - width, err = strconv.Atoi(params.Get("width")) - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"width\" parameter is not a valid integer")) - return - } - } - if _, some := params["height"]; !some { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"width\" parameter missing")) - return - } else { - height, err = strconv.Atoi(params.Get("height")) - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"height\" parameter is not a valid integer")) - return - } - } - } else { - scale, err = strconv.Atoi(params.Get("scale")) - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"scale\" parameter is not a valid integer")) - return + u.Scheme = r.URL.Scheme } + u.Host = r.URL.Host } - imgRes, err := http.Get(u.String()) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) + if _, some := params["threshold"]; !some { + error("\"threshold\" parameter missing", errors.New("Missing argument"), http.StatusBadRequest) return } - if imgRes.StatusCode != 200 { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(fmt.Sprintf("Url returned a status code of %v", imgRes.StatusCode))) + threshold, err := strconv.Atoi("threshold") + if error("\"threshold\" parameter is not a valid integer", err, http.StatusBadRequest) { return } - data, err := io.ReadAll(imgRes.Body) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) + res, err := http.Get(u.String()) + if error("Error trying to fetch the image", err, http.StatusInternalServerError) { + return + } + if res.StatusCode < 200 || res.StatusCode >= 300 { + error( + "Error trying to fetch the image, response is a non 2XX code", + errors.New("Status code: "+res.Status), + http.StatusInternalServerError, + ) + } + + data, err := io.ReadAll(res.Body) + if error("Error trying to read the image data", err, http.StatusInternalServerError) { return } - mime := mime.TypeByExtension(u.String()) - if mime == "" { - mime = http.DetectContentType(data) - } - - var img image.Image - reader := bytes.NewReader(data) - switch mime { - case "image/png": - img, err = png.Decode(reader) - case "image/jpeg": - img, err = jpeg.Decode(reader) - case "image/gif": - img, err = gif.Decode(reader) - case "image/webp": - img, err = webp.Decode(reader) - default: - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("image is not either of \"jpeg\", \"png\", \"gif\", or \"webp\"")) - return - } - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("Error decoding the image:\n" + err.Error())) + img, err := internals.NewImage(data) + if error("Error trying to decode the image", err, http.StatusInternalServerError) { return } - if scale != 0 { - img = Scale(img, float64(scale)) - } else if width > 0 && height > 0 { - img = imaging.Resize(img, width, height, imaging.CatmullRom) - } + img.Optimize(threshold) - switch mime { - case "image/png": - err = png.Encode(w, img) - case "image/jpeg": - err = jpeg.Encode(w, img, &jpeg.Options{Quality: 70}) - case "image/gif": - err = gif.Encode(w, img, &gif.Options{NumColors: 256}) - case "image/webp": - err = webp.Encode(w, img, &webp.Options{Lossless: true}) - default: - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("\"type\" parameter is not either of \"jpeg\", \"png\", \"gif\", or \"webp\"")) - return - } - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("Error encoding the image:\n" + err.Error())) + err = img.Encode(w) + if error("Error trying to encode the image", err, http.StatusInternalServerError) { return } w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400") w.Header().Add("CDN-Cache-Control", "max-age=604800") - w.Header().Add("Content-Type", mime) + w.Header().Add("Content-Type", img.GetMime()) } diff --git a/components/image.templ b/components/image.templ index e6368c1..81b94df 100644 --- a/components/image.templ +++ b/components/image.templ @@ -6,23 +6,23 @@ templ Image(src templ.SafeURL, alt string, class string) { { diff --git a/internals/image.go b/internals/image.go new file mode 100644 index 0000000..7596a51 --- /dev/null +++ b/internals/image.go @@ -0,0 +1,132 @@ +package internals + +import ( + "bytes" + "errors" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "math" + "net/http" + + "github.com/chai2010/webp" + "github.com/disintegration/imaging" +) + +type Image struct { + image.Image + mime string + JpegOptions jpeg.Options + GifOptions gif.Options + WebpOptions webp.Options +} + +func NewImage(b []byte) (Image, error) { + m := http.DetectContentType(b) + + r := bytes.NewReader(b) + + var img image.Image + var err error + + switch m { + case "image/png": + img, err = png.Decode(r) + case "image/jpeg": + img, err = jpeg.Decode(r) + case "image/gif": + img, err = gif.Decode(r) + case "image/webp": + img, err = webp.Decode(r) + default: + err = errors.ErrUnsupported + } + if err != nil { + return Image{}, err + } + + image := img.(Image) + image.mime = m + image.JpegOptions = jpeg.Options{ + Quality: 70, + } + image.GifOptions = gif.Options{ + NumColors: 256 / 2, + } + image.WebpOptions = webp.Options{ + Lossless: true, + Quality: 70.0, + Exact: true, + } + + return image, nil + +} + +func (i *Image) Decode(b []byte) error { + img, err := NewImage(b) + *i = img + return err +} + +func (i *Image) Encode(w io.Writer) error { + var err error + + switch i.mime { + case "image/png": + err = png.Encode(w, i.Image) + case "image/jpeg": + err = jpeg.Encode(w, i.Image, &jpeg.Options{Quality: 70}) + case "image/gif": + err = gif.Encode(w, i.Image, &gif.Options{NumColors: 256}) + case "image/webp": + err = webp.Encode(w, i.Image, &webp.Options{Lossless: true}) + default: + err = errors.ErrUnsupported + } + if err != nil { + return err + } + + return nil +} + +func (i *Image) Quality(q float64) { + i.WebpOptions.Quality = float32(q) + i.JpegOptions.Quality = int(math.Round(q)) +} + +func (i *Image) Optimize(threshold int) { + w := i.Bounds().Max.X + + if threshold >= w { + return + } + + d := threshold / w + i.Scale(d * -1) +} + +func (i *Image) Scale(s int) { + r := i.Bounds() + w, h := r.Max.X, r.Max.Y + + var nw, nh int + if s < 0 { + s = s * -1 + nw, nh = w/s, h/s + } else if s > 0 { + s = s * -1 + nw, nh = w*s, h*s + } else { + nw, nh = w, h + } + + imaging.Resize(i, nw, nh, imaging.CatmullRom) +} + +func (i *Image) GetMime() string { + return i.mime +} From 2aa705489bfacf0f88eac3fac7fdd093fc18b8a5 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 12 Jun 2024 16:15:23 -0300 Subject: [PATCH 2/6] fix: host typo --- api/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/image.go b/api/image.go index 58a09a6..a0d84a9 100644 --- a/api/image.go +++ b/api/image.go @@ -47,7 +47,7 @@ func Image(w http.ResponseWriter, r *http.Request) { } else { u.Scheme = r.URL.Scheme } - u.Host = r.URL.Host + u.Host = r.Host } if _, some := params["threshold"]; !some { From 3edf84449428d08b36fde883f6ccfe2aa1a54410 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 12 Jun 2024 16:15:37 -0300 Subject: [PATCH 3/6] fix: threshold typo/missing getter --- api/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/image.go b/api/image.go index a0d84a9..b6e1baf 100644 --- a/api/image.go +++ b/api/image.go @@ -54,7 +54,7 @@ func Image(w http.ResponseWriter, r *http.Request) { error("\"threshold\" parameter missing", errors.New("Missing argument"), http.StatusBadRequest) return } - threshold, err := strconv.Atoi("threshold") + threshold, err := strconv.Atoi(params.Get("threshold")) if error("\"threshold\" parameter is not a valid integer", err, http.StatusBadRequest) { return } From a2426b8f87a115bfdce4ea72fdcad9fd99675fd7 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 12 Jun 2024 16:19:31 -0300 Subject: [PATCH 4/6] fix: type coersion I should probably have testes this thing before commiting --- internals/image.go | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/internals/image.go b/internals/image.go index 7596a51..8d372f7 100644 --- a/internals/image.go +++ b/internals/image.go @@ -16,7 +16,7 @@ import ( ) type Image struct { - image.Image + img image.Image mime string JpegOptions jpeg.Options GifOptions gif.Options @@ -47,21 +47,21 @@ func NewImage(b []byte) (Image, error) { return Image{}, err } - image := img.(Image) - image.mime = m - image.JpegOptions = jpeg.Options{ - Quality: 70, - } - image.GifOptions = gif.Options{ - NumColors: 256 / 2, - } - image.WebpOptions = webp.Options{ - Lossless: true, - Quality: 70.0, - Exact: true, - } - - return image, nil + return Image{ + img: img, + mime: m, + JpegOptions: jpeg.Options{ + Quality: 70, + }, + GifOptions: gif.Options{ + NumColors: 256, + }, + WebpOptions: webp.Options{ + Lossless: true, + Quality: 70.0, + Exact: true, + }, + }, nil } @@ -76,13 +76,13 @@ func (i *Image) Encode(w io.Writer) error { switch i.mime { case "image/png": - err = png.Encode(w, i.Image) + err = png.Encode(w, i.img) case "image/jpeg": - err = jpeg.Encode(w, i.Image, &jpeg.Options{Quality: 70}) + err = jpeg.Encode(w, i.img, &jpeg.Options{Quality: 70}) case "image/gif": - err = gif.Encode(w, i.Image, &gif.Options{NumColors: 256}) + err = gif.Encode(w, i.img, &gif.Options{NumColors: 256}) case "image/webp": - err = webp.Encode(w, i.Image, &webp.Options{Lossless: true}) + err = webp.Encode(w, i.img, &webp.Options{Lossless: true}) default: err = errors.ErrUnsupported } @@ -99,7 +99,7 @@ func (i *Image) Quality(q float64) { } func (i *Image) Optimize(threshold int) { - w := i.Bounds().Max.X + w := i.img.Bounds().Max.X if threshold >= w { return @@ -110,7 +110,7 @@ func (i *Image) Optimize(threshold int) { } func (i *Image) Scale(s int) { - r := i.Bounds() + r := i.img.Bounds() w, h := r.Max.X, r.Max.Y var nw, nh int @@ -124,7 +124,7 @@ func (i *Image) Scale(s int) { nw, nh = w, h } - imaging.Resize(i, nw, nh, imaging.CatmullRom) + imaging.Resize(i.img, nw, nh, imaging.CatmullRom) } func (i *Image) GetMime() string { From eee1503855e102e88aef9280a23717075e61c710 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 12 Jun 2024 16:30:14 -0300 Subject: [PATCH 5/6] fix: threshold calculation and resize update --- internals/image.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/image.go b/internals/image.go index 8d372f7..bc54cb7 100644 --- a/internals/image.go +++ b/internals/image.go @@ -105,7 +105,7 @@ func (i *Image) Optimize(threshold int) { return } - d := threshold / w + d := w / threshold i.Scale(d * -1) } @@ -124,7 +124,7 @@ func (i *Image) Scale(s int) { nw, nh = w, h } - imaging.Resize(i.img, nw, nh, imaging.CatmullRom) + i.img = imaging.Resize(i.img, nw, nh, imaging.CatmullRom) } func (i *Image) GetMime() string { From 85ea7ddbd09bd10da6ebd9d6e409412ac5fd55b0 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 13 Jun 2024 11:10:21 -0300 Subject: [PATCH 6/6] feat!: convert all images to webp and simplify everything --- api/image.go | 25 +++++++-- go.mod | 17 ++++-- go.sum | 27 ++++++++-- internals/image.go | 132 --------------------------------------------- 4 files changed, 57 insertions(+), 144 deletions(-) delete mode 100644 internals/image.go diff --git a/api/image.go b/api/image.go index b6e1baf..7dfaf39 100644 --- a/api/image.go +++ b/api/image.go @@ -1,14 +1,29 @@ package api import ( + "bytes" "errors" + "image" "io" "net/http" "net/url" "strconv" - "www/internals" + + "github.com/chai2010/webp" + "github.com/sunshineplan/imgconv" ) +func ImgOptimize(i image.Image, threshold int) image.Image { + w := i.Bounds().Max.X + + if threshold >= w { + return i + } + + d := w / threshold + return imgconv.Resize(i, &imgconv.ResizeOption{Width: w / d}) +} + func errorHelper(w http.ResponseWriter) func(msg string, err error, status int) bool { return func(msg string, err error, status int) bool { if err != nil { @@ -76,19 +91,19 @@ func Image(w http.ResponseWriter, r *http.Request) { return } - img, err := internals.NewImage(data) + img, err := imgconv.Decode(bytes.NewReader(data)) if error("Error trying to decode the image", err, http.StatusInternalServerError) { return } - img.Optimize(threshold) + img = ImgOptimize(img, threshold) - err = img.Encode(w) + err = webp.Encode(w, img, &webp.Options{Lossless: true}) if error("Error trying to encode the image", err, http.StatusInternalServerError) { return } w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400") w.Header().Add("CDN-Cache-Control", "max-age=604800") - w.Header().Add("Content-Type", img.GetMime()) + w.Header().Add("Content-Type", "image/webp") } diff --git a/go.mod b/go.mod index d9ba736..9a3d26c 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,21 @@ module www go 1.22.2 -require github.com/a-h/templ v0.2.697 +require ( + github.com/a-h/templ v0.2.697 + github.com/chai2010/webp v1.1.1 + github.com/sunshineplan/imgconv v1.1.10 +) require ( - github.com/chai2010/webp v1.1.1 // indirect - github.com/disintegration/imaging v1.6.2 // indirect + github.com/hhrutter/lzw v1.0.0 // indirect + github.com/hhrutter/tiff v1.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/pdfcpu/pdfcpu v0.8.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sunshineplan/pdf v1.0.7 // indirect golang.org/x/image v0.17.0 // indirect + golang.org/x/text v0.16.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 42d66e2..944bbc6 100644 --- a/go.sum +++ b/go.sum @@ -2,11 +2,30 @@ github.com/a-h/templ v0.2.697 h1:OILxtWvD0NRJaoCOiZCopRDPW8paroKlGsrAiHLykNE= github.com/a-h/templ v0.2.697/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8= github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= -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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0= +github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo= +github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0= +github.com/hhrutter/tiff v1.0.1/go.mod h1:zU/dNgDm0cMIa8y8YwcYBeuEEveI4B0owqHyiPpJPHc= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pdfcpu/pdfcpu v0.8.0 h1:SuEB4uVsPFz1nb802r38YpFpj9TtZh/oB0bGG34IRZw= +github.com/pdfcpu/pdfcpu v0.8.0/go.mod h1:jj03y/KKrwigt5xCi8t7px2mATcKuOzkIOoCX62yMho= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sunshineplan/imgconv v1.1.10 h1:EJZVXLwmvmKgEA1KIJpNSzSssjGaZMdtOLycGFyzxQA= +github.com/sunshineplan/imgconv v1.1.10/go.mod h1:de9NsLFCMW2JVom3mjRZu3GceLFwkEIEkf1EGS5rDX4= +github.com/sunshineplan/pdf v1.0.7 h1:62xlc079jh4tGLDjiihyyhwVFkn0IsxLyDpHplbG9Ew= +github.com/sunshineplan/pdf v1.0.7/go.mod h1:QsEmZCWBE3uFK8PCrM0pua1WDWLNU77YusiDEcY56OQ= golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internals/image.go b/internals/image.go deleted file mode 100644 index bc54cb7..0000000 --- a/internals/image.go +++ /dev/null @@ -1,132 +0,0 @@ -package internals - -import ( - "bytes" - "errors" - "image" - "image/gif" - "image/jpeg" - "image/png" - "io" - "math" - "net/http" - - "github.com/chai2010/webp" - "github.com/disintegration/imaging" -) - -type Image struct { - img image.Image - mime string - JpegOptions jpeg.Options - GifOptions gif.Options - WebpOptions webp.Options -} - -func NewImage(b []byte) (Image, error) { - m := http.DetectContentType(b) - - r := bytes.NewReader(b) - - var img image.Image - var err error - - switch m { - case "image/png": - img, err = png.Decode(r) - case "image/jpeg": - img, err = jpeg.Decode(r) - case "image/gif": - img, err = gif.Decode(r) - case "image/webp": - img, err = webp.Decode(r) - default: - err = errors.ErrUnsupported - } - if err != nil { - return Image{}, err - } - - return Image{ - img: img, - mime: m, - JpegOptions: jpeg.Options{ - Quality: 70, - }, - GifOptions: gif.Options{ - NumColors: 256, - }, - WebpOptions: webp.Options{ - Lossless: true, - Quality: 70.0, - Exact: true, - }, - }, nil - -} - -func (i *Image) Decode(b []byte) error { - img, err := NewImage(b) - *i = img - return err -} - -func (i *Image) Encode(w io.Writer) error { - var err error - - switch i.mime { - case "image/png": - err = png.Encode(w, i.img) - case "image/jpeg": - err = jpeg.Encode(w, i.img, &jpeg.Options{Quality: 70}) - case "image/gif": - err = gif.Encode(w, i.img, &gif.Options{NumColors: 256}) - case "image/webp": - err = webp.Encode(w, i.img, &webp.Options{Lossless: true}) - default: - err = errors.ErrUnsupported - } - if err != nil { - return err - } - - return nil -} - -func (i *Image) Quality(q float64) { - i.WebpOptions.Quality = float32(q) - i.JpegOptions.Quality = int(math.Round(q)) -} - -func (i *Image) Optimize(threshold int) { - w := i.img.Bounds().Max.X - - if threshold >= w { - return - } - - d := w / threshold - i.Scale(d * -1) -} - -func (i *Image) Scale(s int) { - r := i.img.Bounds() - w, h := r.Max.X, r.Max.Y - - var nw, nh int - if s < 0 { - s = s * -1 - nw, nh = w/s, h/s - } else if s > 0 { - s = s * -1 - nw, nh = w*s, h*s - } else { - nw, nh = w, h - } - - i.img = imaging.Resize(i.img, nw, nh, imaging.CatmullRom) -} - -func (i *Image) GetMime() string { - return i.mime -}