func main() { const ( w = 400 h = 400 ) r := raster.NewRasterizer(w, h) r.UseNonZeroWinding = true cjs := []struct { c raster.Capper j raster.Joiner }{ {raster.RoundCapper, raster.RoundJoiner}, {raster.ButtCapper, raster.BevelJoiner}, {raster.SquareCapper, raster.BevelJoiner}, } for i, cj := range cjs { var path raster.Path path.Start(fixed.P(30+100*i, 30+120*i)) path.Add1(fixed.P(180+100*i, 80+120*i)) path.Add1(fixed.P(50+100*i, 130+120*i)) raster.Stroke(r, path, fixed.I(20), cj.c, cj.j) } rgba := image.NewRGBA(image.Rect(0, 0, w, h)) draw.Draw(rgba, rgba.Bounds(), image.Black, image.Point{}, draw.Src) p := raster.NewRGBAPainter(rgba) p.SetColor(color.RGBA{0x7f, 0x7f, 0x7f, 0xff}) r.Rasterize(p) white := color.RGBA{0xff, 0xff, 0xff, 0xff} for i := range cjs { rgba.SetRGBA(30+100*i, 30+120*i, white) rgba.SetRGBA(180+100*i, 80+120*i, white) rgba.SetRGBA(50+100*i, 130+120*i, white) } // Save that RGBA image to disk. outFile, err := os.Create("out.png") if err != nil { log.Println(err) os.Exit(1) } defer outFile.Close() b := bufio.NewWriter(outFile) err = png.Encode(b, rgba) if err != nil { log.Println(err) os.Exit(1) } err = b.Flush() if err != nil { log.Println(err) os.Exit(1) } fmt.Println("Wrote out.png OK.") }
func plotPath(f func(*big.Rat) *big.Rat, fminx, fminy, fmaxx, fmaxy *big.Rat, r fixed.Rectangle26_6) raster.Path { px := r.Min.X var path raster.Path for ; px <= r.Max.X; px += 1 << 6 { fx := new(big.Rat).Add(fminx, new(big.Rat).Mul(new(big.Rat).Sub(fmaxx, fminx), new(big.Rat).Quo(big.NewRat(int64(px-r.Min.X), 1<<6), big.NewRat(int64(r.Max.X-r.Min.X), 1<<6)))) fy := f(fx) fdy := new(big.Rat).Mul(new(big.Rat).Sub(fy, fminy), new(big.Rat).Quo(big.NewRat(int64(r.Max.Y-r.Min.Y), 1), new(big.Rat).Sub(fmaxy, fminy))) dy, _ := fdy.Float64() py := r.Min.Y + fixed.Int26_6(dy+0.5) if len(path) == 0 { path.Start(fixed.Point26_6{px, py}) } else { path.Add1(fixed.Point26_6{px, py}) } } return path }
func (s *Sparkline) Draw(dst draw.Image) { if len(s.data) == 0 { return } min := s.data[0] max := s.data[0] for _, n := range s.data[1:] { if n < min { min = n } if n > max { max = n } } // try to be helpful when there's limited data if max < 1.0 { max = 1.0 } if min > max-1.0 { min = max - 1.0 if min < 0.0 { min = 0.0 } } bounds := dst.Bounds() dx, dy := bounds.Dx(), bounds.Dy() tmp := image.NewRGBA(image.Rectangle{Max: image.Point{X: dx, Y: dy}}) p := raster.NewRGBAPainter(tmp) r := raster.NewRasterizer(dx, dy) r.UseNonZeroWinding = true var q raster.Path q.Start(s.scale(0, s.data[0], min, max, dx, dy)) for i, n := range s.data[1:] { pt := s.scale(i+1, n, min, max, dx, dy) q.Add1(pt) } const strokeWidth = fixed.Int26_6(5 << 6) r.AddStroke(q, strokeWidth, raster.RoundCapper, raster.RoundJoiner) p.SetColor(s.fg) r.Rasterize(p) r.Clear() q.Clear() headPt := s.scale(len(s.data)-1, s.data[len(s.data)-1], min, max, dx, dy) q.Start(headPt) // miniscule nudge so something actually is output q.Add1(headPt.Add(fixed.Point26_6{X: 1, Y: 1})) const headWidth = fixed.Int26_6(8 << 6) r.AddStroke(q, headWidth, raster.RoundCapper, raster.RoundJoiner) // TODO really decide between uint64 vs float32 vs uint32 etc // value := uint64((s.data[len(s.data)-1] - min) / max * float32(^uint64(0))) value := uint64(s.data[len(s.data)-1]) headColor := PickColor(s.thresholds, value) p.SetColor(headColor) r.Rasterize(p) draw.Draw(dst, bounds, tmp, image.ZP, draw.Over) }
func Handler(w http.ResponseWriter, req *http.Request) { const size = 500 c := image.NewRGBA(image.Rect(0, 0, size, size)) white := &image.Uniform{C: color.White} draw.Draw(c, c.Bounds(), white, image.ZP, draw.Src) p := raster.NewRGBAPainter(c) p.SetColor(color.Black) r := raster.NewRasterizer(500, 500) r.UseNonZeroWinding = true var path raster.Path path.Start(nudge(fixed.P(50, 50))) path.Add1(nudge(fixed.P(50, 450))) path.Add1(nudge(fixed.P(450, 450))) r.AddStroke(path, fixed.I(2), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) r.Clear() p.SetColor(color.Gray16{0x7FFF}) path.Clear() r.Clear() path.Start(nudge(fixed.P(450, 450))) path.Add1(nudge(fixed.P(450, 50))) path.Add1(nudge(fixed.P(50, 50))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) p.SetColor(color.Gray16{0x7FFF}) for x := 50; x <= 450; x += 50 { path.Clear() path.Start(nudge(fixed.P(x, 450))) path.Add1(nudge(fixed.P(x, 460))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) } for x := 50; x <= 450; x += 50 { path.Clear() path.Start(nudge(fixed.P(50, x))) path.Add1(nudge(fixed.P(40, x))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) } r.Rasterize(p) p.SetColor(color.RGBA{0x00, 0x00, 0xFF, 0xFF}) r.Clear() path.Clear() path.Start(nudge(fixed.P(50, 450))) path.Add1(nudge(fixed.P(450, 50))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) p.SetColor(color.RGBA{0xCC, 0x00, 0x00, 0xFF}) r.Clear() window := fixed.Rectangle26_6{fixed.P(50, 450), fixed.P(450, 50)} // path = plotPath(ratSin, big.NewRat(0, 1), big.NewRat(-1, 1), big.NewRat(10, 1), big.NewRat(1, 1), window) // path = plotPath(ratProb, big.NewRat(0, 1), big.NewRat(0, 1), big.NewRat(1, 1<<57), big.NewRat(1, 1<<57), window) var lo, hi *big.Rat var locap, hicap, scalecap string id, _ := strconv.Atoi(req.FormValue("x")) switch id { case 0: lo = big.NewRat(1<<53-1<<3, 1<<54) hi = big.NewRat(1<<53+1<<3, 1<<54) locap = "1/2 - 1/2^51" hicap = "1/2 + 1/2^51" scalecap = "1/2^53" case 1: lo = big.NewRat(1<<54-1<<4, 1<<54) hi = big.NewRat(1<<54, 1<<54) locap = "1 - 1/2^50" hicap = "1" scalecap = "1/2^53" case 2: lo = big.NewRat(0, 1<<54) hi = big.NewRat(1<<4, 1<<54) locap = "0" hicap = "1/2^50" scalecap = "1/2^53" case 3: lo = big.NewRat(0, 1<<54) hi = big.NewRat(1<<2, 1<<62) locap = "0" hicap = "1/2^59" scalecap = "1/2^63" } mode, _ := strconv.Atoi(req.FormValue("mode")) path = plotPath(ratProb(mode), lo, lo, hi, hi, window) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) var modestr string switch mode { case 0: modestr = "original behavior (can return 1)" case 1: modestr = "new behavior (too much 0)" case 2: modestr = "retry loop" } data, err := ioutil.ReadFile("/tmp/luxisr.ttf") if err != nil { panic(err) } tfont, err := freetype.ParseFont(data) if err != nil { panic(err) } const pt = 10 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(locap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp := freetype.Pt(50, 480) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(locap, pp) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(hicap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(450, 480) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(hicap, pp) r.Clear() p.SetColor(color.Black) path.Clear() const dy = 5 path.Start(nudge(fixed.P(400, 400))) path.Add1(nudge(fixed.P(400, 400-dy))) path.Add1(nudge(fixed.P(400, 400+dy))) path.Add1(nudge(fixed.P(400, 400))) path.Add1(nudge(fixed.P(450, 400))) path.Add1(nudge(fixed.P(450, 400-dy))) path.Add1(nudge(fixed.P(450, 400+dy))) path.Add1(nudge(fixed.P(450, 400))) r.AddStroke(path, fixed.I(2), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(scalecap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(425, 420) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(scalecap, pp) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(modestr, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(250, 490) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(modestr, pp) w.Write(pngEncode(c)) }