Last active
March 27, 2019 14:38
-
-
Save moos3/8fda09e6ba9a4f57236ff40763ca9d58 to your computer and use it in GitHub Desktop.
on the fly image processing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"flag" | |
"fmt" | |
"image" | |
"image/draw" | |
"image/gif" | |
_ "image/jpeg" | |
_ "image/png" | |
"io" | |
"io/ioutil" | |
"log" | |
"math/rand" | |
"net/http" | |
"os" | |
"os/signal" | |
"runtime" | |
"strconv" | |
"syscall" | |
"time" | |
"github.com/disintegration/imaging" | |
kitlog "github.com/go-kit/kit/log" | |
"github.com/go-kit/kit/metrics" | |
"github.com/gorilla/mux" | |
"golang.org/x/net/context" | |
) | |
func interrupt() error { | |
c := make(chan os.Signal) | |
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |
return fmt.Errorf("%s", <-c) | |
} | |
// getPWD - get current working directory | |
func getPWD() string { | |
dir, err := os.Getwd() | |
if err != nil { | |
log.Fatal(err) | |
} | |
return dir | |
} | |
// Exists - checks if the file ever exists | |
func Exists(name string, staticAsset string) (bool, error) { | |
dir := getPWD() | |
path := dir + staticAsset + name | |
_, err := os.Stat(path) | |
if os.IsNotExist(err) { | |
return false, nil | |
} | |
return true, err | |
} | |
// ResizerParams - struct containing the initial query params | |
type ( | |
ResizerParams struct { | |
file string | |
height int | |
width int | |
fileType string | |
} | |
instrmw struct { | |
requestCount metrics.Counter | |
countResult metrics.Histogram | |
} | |
) | |
// ParseQuery - parse/validate the url params | |
func ParseQuery(r *http.Request) (*ResizerParams, error) { | |
var p ResizerParams | |
query := r.URL.Query() | |
width, _ := strconv.Atoi(query.Get("width")) | |
height, _ := strconv.Atoi(query.Get("height")) | |
if width == 0 { | |
width = -1 | |
} | |
if height == 0 { | |
height = -1 | |
} | |
p = NewResizerParams("", height, width, "") | |
return &p, nil | |
} | |
// NewResizerParams - ResizerParams factory | |
func NewResizerParams(url string, height int, width int, fileType string) ResizerParams { | |
return ResizerParams{url, height, width, fileType} | |
} | |
// getImageInfo - returnes height and width | |
func getImageInfo(imagePath string) (int, int, string) { | |
// just for import "image/png" and "image/jpeg" | |
src0, err := os.Open(imagePath) | |
if err != nil { | |
fmt.Println(err) | |
} | |
defer src0.Close() | |
conf, format, err := image.DecodeConfig(src0) | |
if err != nil { | |
fmt.Println(err) | |
} | |
return conf.Width, conf.Height, format | |
} | |
// FetchAndResizeImage - fetch the image from provided url and resize it | |
func FetchAndResizeImage(p *ResizerParams) (*image.Image, error) { | |
var dst image.Image | |
// Readimage file | |
imgBuffer, err := ioutil.ReadFile(p.file) | |
if err != nil { | |
log.Fatalln("failed to read image") | |
return nil, err | |
} | |
reader := bytes.NewReader(imgBuffer) | |
// fetch config from file | |
width, height, fileType := getImageInfo(p.file) | |
if p.width == -1 { | |
p.width = width | |
} | |
if p.height == -1 { | |
p.height = height | |
} | |
p.fileType = fileType | |
log.Println(p.fileType) | |
// resize input image | |
// decode input data to image | |
src, _, err := image.Decode(reader) | |
if err != nil { | |
return &dst, err | |
} | |
dst = imaging.Resize(src, p.width, p.height, imaging.Lanczos) | |
return &dst, nil | |
} | |
// FetchAndResizeGif - fetch the gif from provided url and resize it | |
func FetchAndResizeGif(p *ResizerParams) (*bytes.Buffer, error) { | |
// Readimage file | |
imgBuffer, err := ioutil.ReadFile(p.file) | |
if err != nil { | |
log.Fatalln("failed to read image") | |
return nil, err | |
} | |
reader := bytes.NewReader(imgBuffer) | |
// fetch config from file | |
width, height, fileType := getImageInfo(p.file) | |
if p.width == -1 { | |
p.width = width | |
} | |
if p.height == -1 { | |
p.height = height | |
} | |
p.fileType = fileType | |
// resize input image | |
g, err := gif.DecodeAll(reader) | |
if err != nil { | |
log.Fatalln("Failed to decode gif file") | |
return nil, err | |
} | |
for i := range g.Image { | |
thumb := imaging.Thumbnail(g.Image[i], p.width, p.height, imaging.Lanczos) | |
g.Image[i] = image.NewPaletted(image.Rect(0, 0, p.width, p.height), g.Image[i].Palette) | |
draw.Draw(g.Image[i], image.Rect(0, 0, p.width, p.height), thumb, image.Pt(0, 0), draw.Src) | |
} | |
c := &bytes.Buffer{} | |
err = gif.EncodeAll(c, g) | |
if err != nil { | |
log.Fatal(err) | |
return nil, err | |
} | |
return c, nil | |
} | |
// EncodeImageToJpg - encode image to jpeg | |
func EncodeImageToJpg(img *image.Image, p *ResizerParams) (*bytes.Buffer, error) { | |
encoded := &bytes.Buffer{} | |
fType := p.fileType | |
format, err := imaging.FormatFromExtension(fType) | |
err = imaging.Encode(encoded, *img, format) | |
return encoded, err | |
} | |
func getRendersHandler(w http.ResponseWriter, r *http.Request) { | |
var ( | |
encoded2 *bytes.Buffer | |
encoded *bytes.Buffer | |
) | |
p, err := ParseQuery(r) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
_, err = Exists(mux.Vars(r)["path"], "/assets/renders/") | |
if err != nil { | |
log.Fatal(err) | |
} | |
dir := getPWD() | |
p.file = dir + "/assets/renders/" + mux.Vars(r)["path"] | |
if p.fileType == "gif" { | |
// fetch input image and resize | |
encoded2, err = FetchAndResizeGif(p) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
} else { | |
// fetch input image and resize | |
img, err := FetchAndResizeImage(p) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
// encode output image to jpeg buffer | |
encoded, err = EncodeImageToJpg(img, p) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
} | |
// set Content-Type and Content-Length headers | |
contentType := "image/" + p.fileType | |
w.Header().Set("Content-Type", contentType) | |
// write the output image to http response body | |
if p.fileType == "gif" { | |
w.Header().Set("Content-Length", strconv.Itoa(encoded2.Len())) | |
_, err = io.Copy(w, encoded2) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
} else { | |
w.Header().Set("Content-Length", strconv.Itoa(encoded.Len())) | |
_, err = io.Copy(w, encoded) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
} | |
} | |
func main() { | |
runtime.GOMAXPROCS(runtime.NumCPU()) | |
// Flag domain | |
fs := flag.NewFlagSet("", flag.ExitOnError) | |
httpAddr := fs.String("http.addr", ":8080", "Address for HTTP server") | |
//debugAddr := fs.String("debug.addr", ":8001", "Address for http debug/instrumentation server") | |
flag.Usage = fs.Usage | |
fs.Parse(os.Args[1:]) | |
// package log domain | |
var logger kitlog.Logger | |
logger = kitlog.NewLogfmtLogger(os.Stderr) | |
// logger = kitlog.NewContext(logger).With("ts", kitlog.DefaultTimestampUTC) | |
// stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) | |
// stdlog.SetFlags(0) | |
// Mechanical stuff | |
rand.Seed(time.Now().UnixNano()) | |
root := context.Background() | |
errc := make(chan error) | |
go func() { | |
errc <- interrupt() | |
}() | |
go func() { | |
_, cancel := context.WithCancel(root) | |
defer cancel() | |
router := mux.NewRouter() | |
router.HandleFunc("/renders/{path:.*}", getRendersHandler).Methods("GET") | |
logger.Log("addr", *httpAddr, "transport", "HTTP") | |
errc <- http.ListenAndServe(*httpAddr, router) | |
}() | |
logger.Log("fatal", <-errc) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment