package web import ( "bytes" "fmt" "image" "image/color" "image/draw" "image/png" "net/http" "strconv" "strings" "code.google.com/p/freetype-go/freetype" "github.com/mattermost/rsc/appfs/fs" "github.com/mattermost/rsc/qr" "github.com/mattermost/rsc/qr/coding" ) func makeImage(req *http.Request, caption, font string, pt, size, border, scale int, f func(x, y int) uint32) *image.RGBA { d := (size + 2*border) * scale csize := 0 if caption != "" { if pt == 0 { pt = 11 } csize = pt * 2 } c := image.NewRGBA(image.Rect(0, 0, d, d+csize)) // white u := &image.Uniform{C: color.White} draw.Draw(c, c.Bounds(), u, image.ZP, draw.Src) for y := 0; y < size; y++ { for x := 0; x < size; x++ { r := image.Rect((x+border)*scale, (y+border)*scale, (x+border+1)*scale, (y+border+1)*scale) rgba := f(x, y) u.C = color.RGBA{byte(rgba >> 24), byte(rgba >> 16), byte(rgba >> 8), byte(rgba)} draw.Draw(c, r, u, image.ZP, draw.Src) } } if csize != 0 { if font == "" { font = "data/luxisr.ttf" } ctxt := fs.NewContext(req) dat, _, err := ctxt.Read(font) if err != nil { panic(err) } tfont, err := freetype.ParseFont(dat) if err != nil { panic(err) } ft := freetype.NewContext() ft.SetDst(c) ft.SetDPI(100) ft.SetFont(tfont) ft.SetFontSize(float64(pt)) ft.SetSrc(image.NewUniform(color.Black)) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err := ft.DrawString(caption, freetype.Pt(0, 0)) if err != nil { panic(err) } p := freetype.Pt(d, d+3*pt/2) p.X -= wid.X p.X /= 2 ft.SetClip(c.Bounds()) ft.DrawString(caption, p) } return c } func makeFrame(req *http.Request, font string, pt, vers, l, scale, dots int) image.Image { lev := coding.Level(l) p, err := coding.NewPlan(coding.Version(vers), lev, 0) if err != nil { panic(err) } nd := p.DataBytes / p.Blocks nc := p.CheckBytes / p.Blocks extra := p.DataBytes - nd*p.Blocks cap := fmt.Sprintf("QR v%d, %s", vers, lev) if dots > 0 { cap = fmt.Sprintf("QR v%d order, from bottom right", vers) } m := makeImage(req, cap, font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 { pix := p.Pixel[y][x] switch pix.Role() { case coding.Data: if dots > 0 { return 0xffffffff } off := int(pix.Offset() / 8) nd := nd var i int for i = 0; i < p.Blocks; i++ { if i == extra { nd++ } if off < nd { break } off -= nd } return blockColors[i%len(blockColors)] case coding.Check: if dots > 0 { return 0xffffffff } i := (int(pix.Offset()/8) - p.DataBytes) / nc return dark(blockColors[i%len(blockColors)]) } if pix&coding.Black != 0 { return 0x000000ff } return 0xffffffff }) if dots > 0 { b := m.Bounds() for y := 0; y <= len(p.Pixel); y++ { for x := 0; x < b.Dx(); x++ { m.SetRGBA(x, y*scale-(y/len(p.Pixel)), color.RGBA{127, 127, 127, 255}) } } for x := 0; x <= len(p.Pixel); x++ { for y := 0; y < b.Dx(); y++ { m.SetRGBA(x*scale-(x/len(p.Pixel)), y, color.RGBA{127, 127, 127, 255}) } } order := make([]image.Point, (p.DataBytes+p.CheckBytes)*8+1) for y, row := range p.Pixel { for x, pix := range row { if r := pix.Role(); r != coding.Data && r != coding.Check { continue } // draw.Draw(m, m.Bounds().Add(image.Pt(x*scale, y*scale)), dot, image.ZP, draw.Over) order[pix.Offset()] = image.Point{x*scale + scale/2, y*scale + scale/2} } } for mode := 0; mode < 2; mode++ { for i, p := range order { q := order[i+1] if q.X == 0 { break } line(m, p, q, mode) } } } return m } func line(m *image.RGBA, p, q image.Point, mode int) { x := 0 y := 0 dx := q.X - p.X dy := q.Y - p.Y xsign := +1 ysign := +1 if dx < 0 { xsign = -1 dx = -dx } if dy < 0 { ysign = -1 dy = -dy } pt := func() { switch mode { case 0: for dx := -2; dx <= 2; dx++ { for dy := -2; dy <= 2; dy++ { if dy*dx <= -4 || dy*dx >= 4 { continue } m.SetRGBA(p.X+x*xsign+dx, p.Y+y*ysign+dy, color.RGBA{255, 192, 192, 255}) } } case 1: m.SetRGBA(p.X+x*xsign, p.Y+y*ysign, color.RGBA{128, 0, 0, 255}) } } if dx > dy { for x < dx || y < dy { pt() x++ if float64(x)*float64(dy)/float64(dx)-float64(y) > 0.5 { y++ } } } else { for x < dx || y < dy { pt() y++ if float64(y)*float64(dx)/float64(dy)-float64(x) > 0.5 { x++ } } } pt() } func pngEncode(c image.Image) []byte { var b bytes.Buffer png.Encode(&b, c) return b.Bytes() } // Frame handles a request for a single QR frame. func Frame(w http.ResponseWriter, req *http.Request) { arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } v := arg("v") scale := arg("scale") if scale == 0 { scale = 8 } w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(pngEncode(makeFrame(req, req.FormValue("font"), arg("pt"), v, arg("l"), scale, arg("dots")))) } // Frames handles a request for multiple QR frames. func Frames(w http.ResponseWriter, req *http.Request) { vs := strings.Split(req.FormValue("v"), ",") arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } scale := arg("scale") if scale == 0 { scale = 8 } font := req.FormValue("font") pt := arg("pt") dots := arg("dots") var images []image.Image l := arg("l") for _, v := range vs { l := l if i := strings.Index(v, "."); i >= 0 { l, _ = strconv.Atoi(v[i+1:]) v = v[:i] } vv, _ := strconv.Atoi(v) images = append(images, makeFrame(req, font, pt, vv, l, scale, dots)) } b := images[len(images)-1].Bounds() dx := arg("dx") if dx == 0 { dx = b.Dx() } x, y := 0, 0 xmax := 0 sep := arg("sep") if sep == 0 { sep = 10 } var points []image.Point for i, m := range images { if x > 0 { x += sep } if x > 0 && x+m.Bounds().Dx() > dx { y += sep + images[i-1].Bounds().Dy() x = 0 } points = append(points, image.Point{x, y}) x += m.Bounds().Dx() if x > xmax { xmax = x } } c := image.NewRGBA(image.Rect(0, 0, xmax, y+b.Dy())) for i, m := range images { draw.Draw(c, c.Bounds().Add(points[i]), m, image.ZP, draw.Src) } w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(pngEncode(c)) } // Mask handles a request for a single QR mask. func Mask(w http.ResponseWriter, req *http.Request) { arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } v := arg("v") m := arg("m") scale := arg("scale") if scale == 0 { scale = 8 } w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(pngEncode(makeMask(req, req.FormValue("font"), arg("pt"), v, m, scale))) } // Masks handles a request for multiple QR masks. func Masks(w http.ResponseWriter, req *http.Request) { arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } v := arg("v") scale := arg("scale") if scale == 0 { scale = 8 } font := req.FormValue("font") pt := arg("pt") var mm []image.Image for m := 0; m < 8; m++ { mm = append(mm, makeMask(req, font, pt, v, m, scale)) } dx := mm[0].Bounds().Dx() dy := mm[0].Bounds().Dy() sep := arg("sep") if sep == 0 { sep = 10 } c := image.NewRGBA(image.Rect(0, 0, (dx+sep)*4-sep, (dy+sep)*2-sep)) for m := 0; m < 8; m++ { x := (m % 4) * (dx + sep) y := (m / 4) * (dy + sep) draw.Draw(c, c.Bounds().Add(image.Pt(x, y)), mm[m], image.ZP, draw.Src) } w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(pngEncode(c)) } var maskName = []string{ "(x+y) % 2", "y % 2", "x % 3", "(x+y) % 3", "(y/2 + x/3) % 2", "xy%2 + xy%3", "(xy%2 + xy%3) % 2", "(xy%3 + (x+y)%2) % 2", } func makeMask(req *http.Request, font string, pt int, vers, mask, scale int) image.Image { p, err := coding.NewPlan(coding.Version(vers), coding.L, coding.Mask(mask)) if err != nil { panic(err) } m := makeImage(req, maskName[mask], font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 { pix := p.Pixel[y][x] switch pix.Role() { case coding.Data, coding.Check: if pix&coding.Invert != 0 { return 0x000000ff } } return 0xffffffff }) return m } var blockColors = []uint32{ 0x7777ffff, 0xffff77ff, 0xff7777ff, 0x77ffffff, 0x1e90ffff, 0xffffe0ff, 0x8b6969ff, 0x77ff77ff, 0x9b30ffff, 0x00bfffff, 0x90e890ff, 0xfff68fff, 0xffec8bff, 0xffa07aff, 0xffa54fff, 0xeee8aaff, 0x98fb98ff, 0xbfbfbfff, 0x54ff9fff, 0xffaeb9ff, 0xb23aeeff, 0xbbffffff, 0x7fffd4ff, 0xff7a7aff, 0x00007fff, } func dark(x uint32) uint32 { r, g, b, a := byte(x>>24), byte(x>>16), byte(x>>8), byte(x) r = r/2 + r/4 g = g/2 + g/4 b = b/2 + b/4 return uint32(r)<<24 | uint32(g)<<16 | uint32(b)<<8 | uint32(a) } func clamp(x int) byte { if x < 0 { return 0 } if x > 255 { return 255 } return byte(x) } func max(x, y int) int { if x > y { return x } return y } // Arrow handles a request for an arrow pointing in a given direction. func Arrow(w http.ResponseWriter, req *http.Request) { arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } dir := arg("dir") size := arg("size") if size == 0 { size = 50 } del := size / 10 m := image.NewRGBA(image.Rect(0, 0, size, size)) if dir == 4 { draw.Draw(m, m.Bounds(), image.Black, image.ZP, draw.Src) draw.Draw(m, image.Rect(5, 5, size-5, size-5), image.White, image.ZP, draw.Src) } pt := func(x, y int, c color.RGBA) { switch dir { case 0: m.SetRGBA(x, y, c) case 1: m.SetRGBA(y, size-1-x, c) case 2: m.SetRGBA(size-1-x, size-1-y, c) case 3: m.SetRGBA(size-1-y, x, c) } } for y := 0; y < size/2; y++ { for x := 0; x < del && x < y; x++ { pt(x, y, color.RGBA{0, 0, 0, 255}) } for x := del; x < y-del; x++ { pt(x, y, color.RGBA{128, 128, 255, 255}) } for x := max(y-del, 0); x <= y; x++ { pt(x, y, color.RGBA{0, 0, 0, 255}) } } for y := size / 2; y < size; y++ { for x := 0; x < del && x < size-1-y; x++ { pt(x, y, color.RGBA{0, 0, 0, 255}) } for x := del; x < size-1-y-del; x++ { pt(x, y, color.RGBA{128, 128, 192, 255}) } for x := max(size-1-y-del, 0); x <= size-1-y; x++ { pt(x, y, color.RGBA{0, 0, 0, 255}) } } w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(pngEncode(m)) } // Encode encodes a string using the given version, level, and mask. func Encode(w http.ResponseWriter, req *http.Request) { val := func(s string) int { v, _ := strconv.Atoi(req.FormValue(s)) return v } l := coding.Level(val("l")) v := coding.Version(val("v")) enc := coding.String(req.FormValue("t")) m := coding.Mask(val("m")) p, err := coding.NewPlan(v, l, m) if err != nil { panic(err) } cc, err := p.Encode(enc) if err != nil { panic(err) } c := &qr.Code{Bitmap: cc.Bitmap, Size: cc.Size, Stride: cc.Stride, Scale: 8} w.Header().Set("Content-Type", "image/png") w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(c.PNG()) }