Permalink
Browse files

simple implementation of filters and http handler

  • Loading branch information...
0 parents commit dde7372a8dc83c3ade46f562b93db01205bacfe1 Chris Kowalik committed with Cubox Feb 27, 2012
Showing with 238 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. BIN bubble.jpg
  3. +15 −0 filter_desaturate.go
  4. +27 −0 filter_sepia.go
  5. +71 −0 handler.go
  6. +28 −0 main.go
  7. +64 −0 picture.go
  8. +32 −0 picture_test.go
@@ -0,0 +1 @@
+/goffects-backend
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "image"
+ "image/color"
+)
+
+func DesaturateFilter(orig image.Image) image.Image {
+ res := image.NewGray(orig.Bounds())
+ EachPixel(orig, func(x, y int, r, g, b, a uint8) {
+ gray := (0.2126 * float32(r)) + (0.7152 * float32(g)) + (0.0722 * float32(b))
+ res.Set(x, y, color.Gray{uint8(gray)})
+ })
+ return res
+}
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "image"
+ "image/color"
+)
+
+func SepiaFilter(orig image.Image) image.Image {
+ res := image.NewRGBA(orig.Bounds())
+ EachPixel(orig, func(x, y int, r, g, b, a uint8) {
+ rt := (float32(r) * 0.393) + (float32(g) * 0.769) + (float32(b) * 0.189)
+ gt := (float32(r) * 0.349) + (float32(g) * 0.686) + (float32(b) * 0.168)
+ bt := (float32(r) * 0.272) + (float32(g) * 0.534) + (float32(b) * 0.131)
+ if rt > 255 {
+ rt = 255
+ }
+ if gt > 255 {
+ gt = 255
+ }
+ if bt > 255 {
+ bt = 255
+ }
+ c := color.RGBA{uint8(rt), uint8(gt), uint8(bt), a}
+ res.Set(x, y, c)
+ })
+ return res
+}
@@ -0,0 +1,71 @@
+package main
+
+import (
+ "image"
+ "image/png"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// Handler is an HTTP handler for serving filtered images.
+type Handler struct{}
+
+// ServeHTTP applies requested filters to the pictures and serves its
+// modified version.
+//
+// w - A response writer.
+// r - Request to be performed.
+//
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ path := filepath.Join(ImagesRoot, r.URL.Path)
+ pic := NewPicture(path)
+
+ if err := pic.Load(); err != nil {
+ if os.IsNotExist(err) {
+ http.NotFound(w, r)
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ r.ParseForm()
+ result := ApplyFiltersBatch(strings.Split(r.FormValue("filter"), ","), pic.Image)
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "image/png")
+ png.Encode(w, result)
+}
+
+// ApplyFiltersBatch applies list of filters to the specified image.
+//
+// filters - The list of filters to apply.
+// img - An image to be processed.
+//
+// Returns modified image.
+func ApplyFiltersBatch(filters []string, img image.Image) (res image.Image) {
+ res = img
+ for _, filter := range filters {
+ res = ApplyFilter(filter, res)
+ }
+ return
+}
+
+// ApplyFilter applies specified filter to the image.
+//
+// filter - The name of the filter to be applied.
+// img - An image to be processed.
+//
+// Returns modified image.
+func ApplyFilter(filter string, img image.Image) (res image.Image) {
+ switch filter {
+ case "desaturate":
+ res = DesaturateFilter(img)
+ case "sepia":
+ res = SepiaFilter(img)
+ default:
+ res = img
+ }
+ return
+}
28 main.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "net/http"
+)
+
+var (
+ // Server will be bound to this address.
+ Addr string
+ // Path to the folder with images.
+ ImagesRoot string
+)
+
+func init() {
+ flag.StringVar(&Addr, "addr", ":8090", "Server will be bound to this address")
+ flag.StringVar(&ImagesRoot, "images-root", "./", "Path to the folder with images")
+ flag.Parse()
+}
+
+func main() {
+ srv := &http.Server{
+ Addr: Addr,
+ Handler: &Handler{},
+ }
+ log.Fatal(srv.ListenAndServe())
+}
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "image"
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
+ "os"
+)
+
+// Picture is a wrapper for the raw image, providing i.a reading image
+// from the file.
+type Picture struct {
+ // Path to the image location.
+ Path string
+ // Raw image object.
+ Image image.Image
+ // The image format.
+ Format string
+}
+
+// NewPicture allocates memory for the picture object.
+//
+// path - The image location path.
+//
+// Returns new picture.
+func NewPicture(path string) *Picture {
+ return &Picture{Path: path}
+}
+
+// Load reads image from the file.
+//
+// Returns an error if something went wrong.
+func (pic *Picture) Load() (err error) {
+ var file *os.File
+ if file, err = os.Open(pic.Path); err != nil {
+ return
+ }
+ pic.Image, pic.Format, err = image.Decode(file)
+ return
+}
+
+// EachPixel is a helper for iterating over all the pixels of given image.
+//
+// img - The image to be iterated over.
+// fn - A callback executed on each position, takes coordinates and color
+// values as arguments.
+//
+// Example:
+//
+// EachPixel(img, func(x, y int, r, g, b, a uint8) {
+// // do something with colors...
+// })
+//
+func EachPixel(img image.Image, fn func(x, y int, r, g, b, a uint8)) {
+ rect := img.Bounds()
+ for i := 0; i < rect.Dx()*rect.Dy(); i++ {
+ x, y := i%rect.Dx()+rect.Min.X, i/rect.Dx()+rect.Min.Y
+ pixel := img.At(x, y)
+ r32, g32, b32, a32 := pixel.RGBA()
+ r8, g8, b8, a8 := uint8(r32), uint8(g32), uint8(b32), uint8(a32)
+ fn(x, y, r8, g8, b8, a8)
+ }
+}
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestNewPicture(t *testing.T) {
+ pic := NewPicture("./test.jpg")
+ if pic.Path != "./test.jpg" {
+ t.Errorf("Expected path to be ./test.jpg, got %s", pic.Path)
+ }
+}
+
+func TestPictureLoadValidFile(t *testing.T) {
+ pic := NewPicture("./bubble.jpg")
+ if err := pic.Load(); err != nil {
+ t.Errorf("Expected to load file without errors, got: %v", err)
+ }
+ if pic.Image == nil {
+ t.Errorf("Expected image to be loaded")
+ }
+ if pic.Format != "jpeg" {
+ t.Errorf("Expected image to be in correct format, got: %s", pic.Format)
+ }
+}
+
+func TestPictureLoadInvalidFile(t *testing.T) {
+ pic := NewPicture("./invalid.jpg")
+ if err := pic.Load(); err == nil {
+ t.Errorf("Expected to get an error while loading invalid file")
+ }
+}

0 comments on commit dde7372

Please sign in to comment.