From c5bad7bcc65ee4c0b287833eeb624d0e50707e02 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sun, 3 Jul 2016 19:24:53 -0500 Subject: [PATCH] it's a box --- color_test.go | 48 +++++++++++++++++++++++++ main.go | 2 ++ server.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 color_test.go diff --git a/color_test.go b/color_test.go new file mode 100644 index 0000000..c53d2e6 --- /dev/null +++ b/color_test.go @@ -0,0 +1,48 @@ +package main + +import ( + "image/color" + "testing" +) + +var ( + white = color.RGBA{0xff, 0xff, 0xff, 0xff} + red = color.RGBA{0xff, 0, 0, 0xff} + green = color.RGBA{0, 0xff, 0, 0xff} + blue = color.RGBA{0, 0, 0xff, 0xff} +) + +func TestParseColor(t *testing.T) { + white := color.RGBA{0xff, 0xff, 0xff, 0xff} + + eq := func(c1, c2 color.Color) bool { + r1, g1, b1, a1 := c1.RGBA() + r2, g2, b2, a2 := c2.RGBA() + return r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2 + } + + type test struct { + in string + out color.RGBA + } + + var tests = []test{ + {"", white}, + {"0000ff", blue}, + {"0000ffff", blue}, + {"00ff00", green}, + {"00ff00ff", green}, + {"ff0000", red}, + {"ff0000ff", red}, + {"FF0000", red}, + } + + for _, tt := range tests { + c := parseColor(tt.in) + if eq(c, tt.out) { + t.Logf("ok: '%s' == %v", tt.in, c) + } else { + t.Errorf("parse color failed: '%s' yielded %v, expected %v", tt.in, c, tt.out) + } + } +} diff --git a/main.go b/main.go index cce4ee5..e5b3125 100644 --- a/main.go +++ b/main.go @@ -17,10 +17,12 @@ func init() { func main() { flag.Parse() + s := server{ out: os.Stdout, errors: os.Stderr, } + if err := http.ListenAndServe(options.Host, &s); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) } diff --git a/server.go b/server.go index b9db8f9..8c82b1e 100644 --- a/server.go +++ b/server.go @@ -2,8 +2,14 @@ package main import ( "fmt" + "image" + "image/color" + "image/draw" + "image/png" "io" "net/http" + "strconv" + "strings" ) type server struct { @@ -17,4 +23,96 @@ func (s *server) logReceived(r *http.Request) { func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.logReceived(r) + encoder, err := getEncoding(r) + if err != nil { + s.writeError(w, err) + return + } + + width := 400 + width_s := r.URL.Query().Get("w") + if width_s != "" { + n, err := strconv.Atoi(width_s) + if err == nil { + width = n + } + } + + height := 400 + height_s := r.URL.Query().Get("h") + if height_s != "" { + n, err := strconv.Atoi(height_s) + if err == nil { + height = n + } + } + + bg := parseColor(r.URL.Query().Get("bg")) + m := image.NewRGBA(image.Rect(0, 0, width, height)) + draw.Draw(m, m.Bounds(), &image.Uniform{bg}, m.Bounds().Min, draw.Src) + encoder.WriteImage(w, m) +} + +func parseColor(c_s string) color.RGBA { + f := func(s string) (n uint8) { + if len(s) != 2 { + return 0 + } + i, err := strconv.ParseUint(s, 16, 0) + if err != nil { + return 0 + } + return uint8(i) + } + + white := color.RGBA{0xff, 0xff, 0xff, 0xff} + c_s = strings.ToLower(c_s) + switch len(c_s) { + case 2: + n := f(c_s[0:2]) + return color.RGBA{n, n, n, 0xff} + case 4: + n, a := f(c_s[0:2]), f(c_s[2:4]) + return color.RGBA{n, n, n, a} + case 6: + return color.RGBA{f(c_s[0:2]), f(c_s[2:4]), f(c_s[4:6]), 0xff} + case 8: + return color.RGBA{f(c_s[0:2]), f(c_s[2:4]), f(c_s[4:6]), f(c_s[6:8])} + default: + return white + } +} + +type imageWriter interface { + WriteImage(w http.ResponseWriter, m image.Image) +} + +type pngWriter struct { + CompressionLevel png.CompressionLevel +} + +func (w pngWriter) WriteImage(rw http.ResponseWriter, m image.Image) { + enc := png.Encoder{CompressionLevel: w.CompressionLevel} + rw.Header().Add("Content-Type", "image/png") + enc.Encode(rw, m) +} + +func getEncoding(r *http.Request) (imageWriter, error) { + parts := strings.Split(r.URL.Path, ".") + if len(parts) == 0 { + return nil, fmt.Errorf("no encoding specified") + } + + switch e := parts[len(parts)-1]; e { + case "png": + return pngWriter{CompressionLevel: png.DefaultCompression}, nil + default: + return nil, fmt.Errorf("invalid encoding: %s", e) + } +} + +func (s *server) writeError(w http.ResponseWriter, err error) { + fmt.Fprintf(s.errors, "E %s\n", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) }