Skip to content

Instantly share code, notes, and snippets.

@moos3
Last active March 27, 2019 14:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moos3/8fda09e6ba9a4f57236ff40763ca9d58 to your computer and use it in GitHub Desktop.
Save moos3/8fda09e6ba9a4f57236ff40763ca9d58 to your computer and use it in GitHub Desktop.
on the fly image processing
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