diff options
Diffstat (limited to 'vendor/github.com/fogleman')
28 files changed, 1914 insertions, 0 deletions
diff --git a/vendor/github.com/fogleman/gg/LICENSE.md b/vendor/github.com/fogleman/gg/LICENSE.md new file mode 100644 index 0000000..d7b4099 --- /dev/null +++ b/vendor/github.com/fogleman/gg/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (C) 2016 Michael Fogleman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fogleman/gg/bezier.go b/vendor/github.com/fogleman/gg/bezier.go new file mode 100644 index 0000000..f2cd7ab --- /dev/null +++ b/vendor/github.com/fogleman/gg/bezier.go @@ -0,0 +1,59 @@ +package gg + +import "math" + +func quadratic(x0, y0, x1, y1, x2, y2, t float64) (x, y float64) { + u := 1 - t + a := u * u + b := 2 * u * t + c := t * t + x = a*x0 + b*x1 + c*x2 + y = a*y0 + b*y1 + c*y2 + return +} + +func QuadraticBezier(x0, y0, x1, y1, x2, y2 float64) []Point { + l := (math.Hypot(x1-x0, y1-y0) + + math.Hypot(x2-x1, y2-y1)) + n := int(l + 0.5) + if n < 4 { + n = 4 + } + d := float64(n) - 1 + result := make([]Point, n) + for i := 0; i < n; i++ { + t := float64(i) / d + x, y := quadratic(x0, y0, x1, y1, x2, y2, t) + result[i] = Point{x, y} + } + return result +} + +func cubic(x0, y0, x1, y1, x2, y2, x3, y3, t float64) (x, y float64) { + u := 1 - t + a := u * u * u + b := 3 * u * u * t + c := 3 * u * t * t + d := t * t * t + x = a*x0 + b*x1 + c*x2 + d*x3 + y = a*y0 + b*y1 + c*y2 + d*y3 + return +} + +func CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3 float64) []Point { + l := (math.Hypot(x1-x0, y1-y0) + + math.Hypot(x2-x1, y2-y1) + + math.Hypot(x3-x2, y3-y2)) + n := int(l + 0.5) + if n < 4 { + n = 4 + } + d := float64(n) - 1 + result := make([]Point, n) + for i := 0; i < n; i++ { + t := float64(i) / d + x, y := cubic(x0, y0, x1, y1, x2, y2, x3, y3, t) + result[i] = Point{x, y} + } + return result +} diff --git a/vendor/github.com/fogleman/gg/context.go b/vendor/github.com/fogleman/gg/context.go new file mode 100644 index 0000000..e186027 --- /dev/null +++ b/vendor/github.com/fogleman/gg/context.go @@ -0,0 +1,738 @@ +// Package gg provides a simple API for rendering 2D graphics in pure Go. +package gg + +import ( + "image" + "image/color" + "image/draw" + "image/png" + "io" + "math" + + "github.com/golang/freetype/raster" + "golang.org/x/image/font" + "golang.org/x/image/font/basicfont" +) + +type LineCap int + +const ( + LineCapRound LineCap = iota + LineCapButt + LineCapSquare +) + +type LineJoin int + +const ( + LineJoinRound LineJoin = iota + LineJoinBevel +) + +type FillRule int + +const ( + FillRuleWinding FillRule = iota + FillRuleEvenOdd +) + +type Align int + +const ( + AlignLeft Align = iota + AlignCenter + AlignRight +) + +type Context struct { + width int + height int + im *image.RGBA + mask *image.Alpha + color color.Color + strokePath raster.Path + fillPath raster.Path + start Point + current Point + hasCurrent bool + dashes []float64 + lineWidth float64 + lineCap LineCap + lineJoin LineJoin + fillRule FillRule + fontFace font.Face + fontHeight float64 + matrix Matrix + stack []*Context +} + +// NewContext creates a new image.RGBA with the specified width and height +// and prepares a context for rendering onto that image. +func NewContext(width, height int) *Context { + return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height))) +} + +// NewContextForImage copies the specified image into a new image.RGBA +// and prepares a context for rendering onto that image. +func NewContextForImage(im image.Image) *Context { + return NewContextForRGBA(imageToRGBA(im)) +} + +// NewContextForRGBA prepares a context for rendering onto the specified image. +// No copy is made. +func NewContextForRGBA(im *image.RGBA) *Context { + return &Context{ + width: im.Bounds().Size().X, + height: im.Bounds().Size().Y, + im: im, + color: color.Transparent, + lineWidth: 1, + fillRule: FillRuleWinding, + fontFace: basicfont.Face7x13, + fontHeight: 13, + matrix: Identity(), + } +} + +// Image returns the image that has been drawn by this context. +func (dc *Context) Image() image.Image { + return dc.im +} + +// Width returns the width of the image in pixels. +func (dc *Context) Width() int { + return dc.width +} + +// Height returns the height of the image in pixels. +func (dc *Context) Height() int { + return dc.height +} + +// SavePNG encodes the image as a PNG and writes it to disk. +func (dc *Context) SavePNG(path string) error { + return SavePNG(path, dc.im) +} + +// EncodePNG encodes the image as a PNG and writes it to the provided io.Writer. +func (dc *Context) EncodePNG(w io.Writer) error { + return png.Encode(w, dc.im) +} + +// SetDash sets the current dash pattern to use. Call with zero arguments to +// disable dashes. The values specify the lengths of each dash, with +// alternating on and off lengths. +func (dc *Context) SetDash(dashes ...float64) { + dc.dashes = dashes +} + +func (dc *Context) SetLineWidth(lineWidth float64) { + dc.lineWidth = lineWidth +} + +func (dc *Context) SetLineCap(lineCap LineCap) { + dc.lineCap = lineCap +} + +func (dc *Context) SetLineCapRound() { + dc.lineCap = LineCapRound +} + +func (dc *Context) SetLineCapButt() { + dc.lineCap = LineCapButt +} + +func (dc *Context) SetLineCapSquare() { + dc.lineCap = LineCapSquare +} + +func (dc *Context) SetLineJoin(lineJoin LineJoin) { + dc.lineJoin = lineJoin +} + +func (dc *Context) SetLineJoinRound() { + dc.lineJoin = LineJoinRound +} + +func (dc *Context) SetLineJoinBevel() { + dc.lineJoin = LineJoinBevel +} + +func (dc *Context) SetFillRule(fillRule FillRule) { + dc.fillRule = fillRule +} + +func (dc *Context) SetFillRuleWinding() { + dc.fillRule = FillRuleWinding +} + +func (dc *Context) SetFillRuleEvenOdd() { + dc.fillRule = FillRuleEvenOdd +} + +// Color Setters + +// SetColor sets the current color. +func (dc *Context) SetColor(c color.Color) { + dc.color = c +} + +// SetHexColor sets the current color using a hex string. The leading pound +// sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits +// may be provided to set the alpha value as well. +func (dc *Context) SetHexColor(x string) { + r, g, b, a := parseHexColor(x) + dc.SetRGBA255(r, g, b, a) +} + +// SetRGBA255 sets the current color. r, g, b, a values should be between 0 and +// 255, inclusive. +func (dc *Context) SetRGBA255(r, g, b, a int) { + dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} +} + +// SetRGB255 sets the current color. r, g, b values should be between 0 and 255, +// inclusive. Alpha will be set to 255 (fully opaque). +func (dc *Context) SetRGB255(r, g, b int) { + dc.SetRGBA255(r, g, b, 255) +} + +// SetRGBA sets the current color. r, g, b, a values should be between 0 and 1, +// inclusive. +func (dc *Context) SetRGBA(r, g, b, a float64) { + dc.color = color.NRGBA{ + uint8(r * 255), + uint8(g * 255), + uint8(b * 255), + uint8(a * 255), + } +} + +// SetRGB sets the current color. r, g, b values should be between 0 and 1, +// inclusive. Alpha will be set to 1 (fully opaque). +func (dc *Context) SetRGB(r, g, b float64) { + dc.SetRGBA(r, g, b, 1) +} + +// Path Manipulation + +// MoveTo starts a new subpath within the current path starting at the +// specified point. +func (dc *Context) MoveTo(x, y float64) { + if dc.hasCurrent { + dc.fillPath.Add1(dc.start.Fixed()) + } + x, y = dc.TransformPoint(x, y) + p := Point{x, y} + dc.strokePath.Start(p.Fixed()) + dc.fillPath.Start(p.Fixed()) + dc.start = p + dc.current = p + dc.hasCurrent = true +} + +// LineTo adds a line segment to the current path starting at the current +// point. If there is no current point, it is equivalent to MoveTo(x, y) +func (dc *Context) LineTo(x, y float64) { + if !dc.hasCurrent { + dc.MoveTo(x, y) + } else { + x, y = dc.TransformPoint(x, y) + p := Point{x, y} + dc.strokePath.Add1(p.Fixed()) + dc.fillPath.Add1(p.Fixed()) + dc.current = p + } +} + +// QuadraticTo adds a quadratic bezier curve to the current path starting at +// the current point. If there is no current point, it first performs +// MoveTo(x1, y1) +func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) { + if !dc.hasCurrent { + dc.MoveTo(x1, y1) + } + x1, y1 = dc.TransformPoint(x1, y1) + x2, y2 = dc.TransformPoint(x2, y2) + p1 := Point{x1, y1} + p2 := Point{x2, y2} + dc.strokePath.Add2(p1.Fixed(), p2.Fixed()) + dc.fillPath.Add2(p1.Fixed(), p2.Fixed()) + dc.current = p2 +} + +// CubicTo adds a cubic bezier curve to the current path starting at the +// current point. If there is no current point, it first performs +// MoveTo(x1, y1). Because freetype/raster does not support cubic beziers, +// this is emulated with many small line segments. +func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) { + if !dc.hasCurrent { + dc.MoveTo(x1, y1) + } + x0, y0 := dc.current.X, dc.current.Y + x1, y1 = dc.TransformPoint(x1, y1) + x2, y2 = dc.TransformPoint(x2, y2) + x3, y3 = dc.TransformPoint(x3, y3) + points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3) + previous := dc.current.Fixed() + for _, p := range points[1:] { + f := p.Fixed() + if f == previous { + // TODO: this fixes some rendering issues but not all + continue + } + previous = f + dc.strokePath.Add1(f) + dc.fillPath.Add1(f) + dc.current = p + } +} + +// ClosePath adds a line segment from the current point to the beginning +// of the current subpath. If there is no current point, this is a no-op. +func (dc *Context) ClosePath() { + if dc.hasCurrent { + dc.strokePath.Add1(dc.start.Fixed()) + dc.fillPath.Add1(dc.start.Fixed()) + dc.current = dc.start + } +} + +// ClearPath clears the current path. There is no current point after this +// operation. +func (dc *Context) ClearPath() { + dc.strokePath.Clear() + dc.fillPath.Clear() + dc.hasCurrent = false +} + +// NewSubPath starts a new subpath within the current path. There is no current +// point after this operation. +func (dc *Context) NewSubPath() { + if dc.hasCurrent { + dc.fillPath.Add1(dc.start.Fixed()) + } + dc.hasCurrent = false +} + +// Path Drawing + +func (dc *Context) capper() raster.Capper { + switch dc.lineCap { + case LineCapButt: + return raster.ButtCapper + case LineCapRound: + return raster.RoundCapper + case LineCapSquare: + return raster.SquareCapper + } + return nil +} + +func (dc *Context) joiner() raster.Joiner { + switch dc.lineJoin { + case LineJoinBevel: + return raster.BevelJoiner + case LineJoinRound: + return raster.RoundJoiner + } + return nil +} + +func (dc *Context) stroke(painter raster.Painter) { + path := dc.strokePath + if len(dc.dashes) > 0 { + path = dashed(path, dc.dashes) + } else { + // TODO: this is a temporary workaround to remove tiny segments + // that result in rendering issues + path = rasterPath(flattenPath(path)) + } + r := raster.NewRasterizer(dc.width, dc.height) + r.UseNonZeroWinding = true + r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner()) + r.Rasterize(painter) +} + +func (dc *Context) fill(painter raster.Painter) { + path := dc.fillPath + if dc.hasCurrent { + path = make(raster.Path, len(dc.fillPath)) + copy(path, dc.fillPath) + path.Add1(dc.start.Fixed()) + } + r := raster.NewRasterizer(dc.width, dc.height) + r.UseNonZeroWinding = dc.fillRule == FillRuleWinding + r.AddPath(path) + r.Rasterize(painter) +} + +// StrokePreserve strokes the current path with the current color, line width, +// line cap, line join and dash settings. The path is preserved after this +// operation. +func (dc *Context) StrokePreserve() { + if dc.mask == nil { + painter := raster.NewRGBAPainter(dc.im) + painter.SetColor(dc.color) + dc.stroke(painter) + } else { + im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) + painter := raster.NewRGBAPainter(im) + painter.SetColor(dc.color) + dc.stroke(painter) + draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) + } +} + +// Stroke strokes the current path with the current color, line width, +// line cap, line join and dash settings. The path is cleared after this +// operation. +func (dc *Context) Stroke() { + dc.StrokePreserve() + dc.ClearPath() +} + +// FillPreserve fills the current path with the current color. Open subpaths +// are implicity closed. The path is preserved after this operation. +func (dc *Context) FillPreserve() { + if dc.mask == nil { + painter := raster.NewRGBAPainter(dc.im) + painter.SetColor(dc.color) + dc.fill(painter) + } else { + im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) + painter := raster.NewRGBAPainter(im) + painter.SetColor(dc.color) + dc.fill(painter) + draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) + } +} + +// Fill fills the current path with the current color. Open subpaths +// are implicity closed. The path is cleared after this operation. +func (dc *Context) Fill() { + dc.FillPreserve() + dc.ClearPath() +} + +// ClipPreserve updates the clipping region by intersecting the current +// clipping region with the current path as it would be filled by dc.Fill(). +// The path is preserved after this operation. +func (dc *Context) ClipPreserve() { + clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height)) + painter := raster.NewAlphaOverPainter(clip) + dc.fill(painter) + if dc.mask == nil { + dc.mask = clip + } else { + mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height)) + draw.DrawMask(mask, mask.Bounds(), clip, image.ZP, dc.mask, image.ZP, draw.Over) + dc.mask = mask + } +} + +// Clip updates the clipping region by intersecting the current +// clipping region with the current path as it would be filled by dc.Fill(). +// The path is cleared after this operation. +func (dc *Context) Clip() { + dc.ClipPreserve() + dc.ClearPath() +} + +// ResetClip clears the clipping region. +func (dc *Context) ResetClip() { + dc.mask = nil +} + +// Convenient Drawing Functions + +// Clear fills the entire image with the current color. +func (dc *Context) Clear() { + src := image.NewUniform(dc.color) + draw.Draw(dc.im, dc.im.Bounds(), src, image.ZP, draw.Src) +} + +func (dc *Context) DrawLine(x1, y1, x2, y2 float64) { + dc.MoveTo(x1, y1) + dc.LineTo(x2, y2) +} + +func (dc *Context) DrawRectangle(x, y, w, h float64) { + dc.NewSubPath() + dc.MoveTo(x, y) + dc.LineTo(x+w, y) + dc.LineTo(x+w, y+h) + dc.LineTo(x, y+h) + dc.ClosePath() +} + +func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) { + x0, x1, x2, x3 := x, x+r, x+w-r, x+w + y0, y1, y2, y3 := y, y+r, y+h-r, y+h + dc.NewSubPath() + dc.MoveTo(x1, y0) + dc.LineTo(x2, y0) + dc.DrawArc(x2, y1, r, Radians(270), Radians(360)) + dc.LineTo(x3, y2) + dc.DrawArc(x2, y2, r, Radians(0), Radians(90)) + dc.LineTo(x1, y3) + dc.DrawArc(x1, y2, r, Radians(90), Radians(180)) + dc.LineTo(x0, y1) + dc.DrawArc(x1, y1, r, Radians(180), Radians(270)) + dc.ClosePath() +} + +func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) { + const n = 16 + for i := 0; i < n; i++ { + p1 := float64(i+0) / n + p2 := float64(i+1) / n + a1 := angle1 + (angle2-angle1)*p1 + a2 := angle1 + (angle2-angle1)*p2 + x0 := x + rx*math.Cos(a1) + y0 := y + ry*math.Sin(a1) + x1 := x + rx*math.Cos(a1+(a2-a1)/2) + y1 := y + ry*math.Sin(a1+(a2-a1)/2) + x2 := x + rx*math.Cos(a2) + y2 := y + ry*math.Sin(a2) + cx := 2*x1 - x0/2 - x2/2 + cy := 2*y1 - y0/2 - y2/2 + if i == 0 && !dc.hasCurrent { + dc.MoveTo(x0, y0) + } + dc.QuadraticTo(cx, cy, x2, y2) + } +} + +func (dc *Context) DrawEllipse(x, y, rx, ry float64) { + dc.NewSubPath() + dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi) + dc.ClosePath() +} + +func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) { + dc.DrawEllipticalArc(x, y, r, r, angle1, angle2) +} + +func (dc *Context) DrawCircle(x, y, r float64) { + dc.NewSubPath() + dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi) + dc.ClosePath() +} + +func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) { + angle := 2 * math.Pi / float64(n) + rotation -= math.Pi / 2 + if n%2 == 0 { + rotation += angle / 2 + } + dc.NewSubPath() + for i := 0; i < n; i++ { + a := rotation + angle*float64(i) + dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a)) + } + dc.ClosePath() +} + +// DrawImage draws the specified image at the specified point. +// Currently, rotation and scaling transforms are not supported. +func (dc *Context) DrawImage(im image.Image, x, y int) { + dc.DrawImageAnchored(im, x, y, 0, 0) +} + +// DrawImageAnchored draws the specified image at the specified anchor point. +// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the +// image. Use ax=0.5, ay=0.5 to center the image at the specified point. +func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) { + s := im.Bounds().Size() + x -= int(ax * float64(s.X)) + y -= int(ay * float64(s.Y)) + p := image.Pt(x, y) + r := image.Rectangle{p, p.Add(s)} + if dc.mask == nil { + draw.Draw(dc.im, r, im, image.ZP, draw.Over) + } else { + draw.DrawMask(dc.im, r, im, image.ZP, dc.mask, p, draw.Over) + } +} + +// Text Functions + +func (dc *Context) SetFontFace(fontFace font.Face) { + dc.fontFace = fontFace +} + +func (dc *Context) LoadFontFace(path string, points float64) error { + face, err := loadFontFace(path, points) + if err == nil { + dc.fontFace = face + dc.fontHeight = points * 72 / 96 + } + return err +} + +func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) { + d := &font.Drawer{ + Dst: im, + Src: image.NewUniform(dc.color), + Face: dc.fontFace, + Dot: fixp(x, y), + } + d.DrawString(s) +} + +// DrawString draws the specified text at the specified point. +// Currently, rotation and scaling transforms are not supported. +func (dc *Context) DrawString(s string, x, y float64) { + dc.DrawStringAnchored(s, x, y, 0, 0) +} + +// DrawStringAnchored draws the specified text at the specified anchor point. +// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the +// text. Use ax=0.5, ay=0.5 to center the text at the specified point. +func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) { + w, h := dc.MeasureString(s) + x, y = dc.TransformPoint(x, y) + x -= ax * w + y += ay * h + if dc.mask == nil { + dc.drawString(dc.im, s, x, y) + } else { + im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) + dc.drawString(im, s, x, y) + draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) + } +} + +// DrawStringWrapped word-wraps the specified string to the given max width +// and then draws it at the specified anchor point using the given line +// spacing and text alignment. +func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) { + lines := dc.WordWrap(s, width) + h := float64(len(lines)) * dc.fontHeight * lineSpacing + h -= (lineSpacing - 1) * dc.fontHeight + x -= ax * width + y -= ay * h + switch align { + case AlignLeft: + ax = 0 + case AlignCenter: + ax = 0.5 + x += width / 2 + case AlignRight: + ax = 1 + x += width + } + ay = 1 + for _, line := range lines { + dc.DrawStringAnchored(line, x, y, ax, ay) + y += dc.fontHeight * lineSpacing + } +} + +// MeasureString returns the rendered width and height of the specified text +// given the current font face. +func (dc *Context) MeasureString(s string) (w, h float64) { + d := &font.Drawer{ + Face: dc.fontFace, + } + a := d.MeasureString(s) + return float64(a >> 6), dc.fontHeight +} + +// WordWrap wraps the specified string to the given max width and current +// font face. +func (dc *Context) WordWrap(s string, w float64) []string { + return wordWrap(dc, s, w) +} + +// Transformation Matrix Operations + +// Identity resets the current transformation matrix to the identity matrix. +// This results in no translating, scaling, rotating, or shearing. +func (dc *Context) Identity() { + dc.matrix = Identity() +} + +// Translate updates the current matrix with a translation. +func (dc *Context) Translate(x, y float64) { + dc.matrix = dc.matrix.Translate(x, y) +} + +// Scale updates the current matrix with a scaling factor. +// Scaling occurs about the origin. +func (dc *Context) Scale(x, y float64) { + dc.matrix = dc.matrix.Scale(x, y) +} + +// ScaleAbout updates the current matrix with a scaling factor. +// Scaling occurs about the specified point. +func (dc *Context) ScaleAbout(sx, sy, x, y float64) { + dc.Translate(x, y) + dc.Scale(sx, sy) + dc.Translate(-x, -y) +} + +// Rotate updates the current matrix with a clockwise rotation. +// Rotation occurs about the origin. Angle is specified in radians. +func (dc *Context) Rotate(angle float64) { + dc.matrix = dc.matrix.Rotate(angle) +} + +// RotateAbout updates the current matrix with a clockwise rotation. +// Rotation occurs about the specified point. Angle is specified in radians. +func (dc *Context) RotateAbout(angle, x, y float64) { + dc.Translate(x, y) + dc.Rotate(angle) + dc.Translate(-x, -y) +} + +// Shear updates the current matrix with a shearing angle. +// Shearing occurs about the origin. +func (dc *Context) Shear(x, y float64) { + dc.matrix = dc.matrix.Shear(x, y) +} + +// ShearAbout updates the current matrix with a shearing angle. +// Shearing occurs about the specified point. +func (dc *Context) ShearAbout(sx, sy, x, y float64) { + dc.Translate(x, y) + dc.Shear(sx, sy) + dc.Translate(-x, -y) +} + +// TransformPoint multiplies the specified point by the current matrix, +// returning a transformed position. +func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) { + return dc.matrix.TransformPoint(x, y) +} + +// InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at +// the bottom of the image. +func (dc *Context) InvertY() { + dc.Translate(0, float64(dc.height)) + dc.Scale(1, -1) +} + +// Stack + +// Push saves the current state of the context for later retrieval. These +// can be nested. +func (dc *Context) Push() { + x := *dc + dc.stack = append(dc.stack, &x) +} + +// Pop restores the last saved context state from the stack. +func (dc *Context) Pop() { + before := *dc + s := dc.stack + x, s := s[len(s)-1], s[:len(s)-1] + *dc = *x + dc.mask = before.mask + dc.strokePath = before.strokePath + dc.fillPath = before.fillPath + dc.start = before.start + dc.current = before.current + dc.hasCurrent = before.hasCurrent +} diff --git a/vendor/github.com/fogleman/gg/examples/beziers.go b/vendor/github.com/fogleman/gg/examples/beziers.go new file mode 100644 index 0000000..e4cfa8d --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/beziers.go @@ -0,0 +1,84 @@ +package main + +import ( + "math/rand" + + "github.com/fogleman/gg" +) + +func random() float64 { + return rand.Float64()*2 - 1 +} + +func point() (x, y float64) { + return random(), random() +} + +func drawCurve(dc *gg.Context) { + dc.SetRGBA(0, 0, 0, 0.1) + dc.FillPreserve() + dc.SetRGB(0, 0, 0) + dc.SetLineWidth(12) + dc.Stroke() +} + +func drawPoints(dc *gg.Context) { + dc.SetRGBA(1, 0, 0, 0.5) + dc.SetLineWidth(2) + dc.Stroke() +} + +func randomQuadratic(dc *gg.Context) { + x0, y0 := point() + x1, y1 := point() + x2, y2 := point() + dc.MoveTo(x0, y0) + dc.QuadraticTo(x1, y1, x2, y2) + drawCurve(dc) + dc.MoveTo(x0, y0) + dc.LineTo(x1, y1) + dc.LineTo(x2, y2) + drawPoints(dc) +} + +func randomCubic(dc *gg.Context) { + x0, y0 := point() + x1, y1 := point() + x2, y2 := point() + x3, y3 := point() + dc.MoveTo(x0, y0) + dc.CubicTo(x1, y1, x2, y2, x3, y3) + drawCurve(dc) + dc.MoveTo(x0, y0) + dc.LineTo(x1, y1) + dc.LineTo(x2, y2) + dc.LineTo(x3, y3) + drawPoints(dc) +} + +func main() { + const ( + S = 256 + W = 8 + H = 8 + ) + dc := gg.NewContext(S*W, S*H) + dc.SetRGB(1, 1, 1) + dc.Clear() + for j := 0; j < H; j++ { + for i := 0; i < W; i++ { + x := float64(i)*S + S/2 + y := float64(j)*S + S/2 + dc.Push() + dc.Translate(x, y) + dc.Scale(S/2, S/2) + if j%2 == 0 { + randomCubic(dc) + } else { + randomQuadratic(dc) + } + dc.Pop() + } + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/circle.go b/vendor/github.com/fogleman/gg/examples/circle.go new file mode 100644 index 0000000..5debf66 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/circle.go @@ -0,0 +1,11 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + dc := gg.NewContext(1000, 1000) + dc.DrawCircle(500, 500, 400) + dc.SetRGB(0, 0, 0) + dc.Fill() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/clip.go b/vendor/github.com/fogleman/gg/examples/clip.go new file mode 100644 index 0000000..73e6f28 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/clip.go @@ -0,0 +1,15 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + dc := gg.NewContext(1000, 1000) + dc.DrawCircle(350, 500, 300) + dc.Clip() + dc.DrawCircle(650, 500, 300) + dc.Clip() + dc.DrawRectangle(0, 0, 1000, 1000) + dc.SetRGB(0, 0, 0) + dc.Fill() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/cubic.go b/vendor/github.com/fogleman/gg/examples/cubic.go new file mode 100644 index 0000000..bedc585 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/cubic.go @@ -0,0 +1,38 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1000 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.Translate(S/2, S/2) + dc.Scale(40, 40) + + var x0, y0, x1, y1, x2, y2, x3, y3 float64 + x0, y0 = -10, 0 + x1, y1 = -8, -8 + x2, y2 = 8, 8 + x3, y3 = 10, 0 + + dc.MoveTo(x0, y0) + dc.CubicTo(x1, y1, x2, y2, x3, y3) + dc.SetRGBA(0, 0, 0, 0.2) + dc.SetLineWidth(8) + dc.FillPreserve() + dc.SetRGB(0, 0, 0) + dc.SetDash(16, 24) + dc.Stroke() + + dc.MoveTo(x0, y0) + dc.LineTo(x1, y1) + dc.LineTo(x2, y2) + dc.LineTo(x3, y3) + dc.SetRGBA(1, 0, 0, 0.4) + dc.SetLineWidth(2) + dc.SetDash(4, 8, 1, 8) + dc.Stroke() + + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/ellipse.go b/vendor/github.com/fogleman/gg/examples/ellipse.go new file mode 100644 index 0000000..e0de9f4 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/ellipse.go @@ -0,0 +1,20 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1024 + dc := gg.NewContext(S, S) + dc.SetRGBA(0, 0, 0, 0.1) + for i := 0; i < 360; i += 15 { + dc.Push() + dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2) + dc.DrawEllipse(S/2, S/2, S*7/16, S/8) + dc.Fill() + dc.Pop() + } + if im, err := gg.LoadImage("examples/gopher.png"); err == nil { + dc.DrawImageAnchored(im, S/2, S/2, 0.5, 0.5) + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/lines.go b/vendor/github.com/fogleman/gg/examples/lines.go new file mode 100644 index 0000000..9581dd6 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/lines.go @@ -0,0 +1,31 @@ +package main + +import ( + "math/rand" + + "github.com/fogleman/gg" +) + +func main() { + const W = 1024 + const H = 1024 + dc := gg.NewContext(W, H) + dc.SetRGB(0, 0, 0) + dc.Clear() + for i := 0; i < 1000; i++ { + x1 := rand.Float64() * W + y1 := rand.Float64() * H + x2 := rand.Float64() * W + y2 := rand.Float64() * H + r := rand.Float64() + g := rand.Float64() + b := rand.Float64() + a := rand.Float64()*0.5 + 0.5 + w := rand.Float64()*4 + 1 + dc.SetRGBA(r, g, b, a) + dc.SetLineWidth(w) + dc.DrawLine(x1, y1, x2, y2) + dc.Stroke() + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/linewidth.go b/vendor/github.com/fogleman/gg/examples/linewidth.go new file mode 100644 index 0000000..8cdfdca --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/linewidth.go @@ -0,0 +1,19 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + dc := gg.NewContext(1000, 1000) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) + w := 0.1 + for i := 100; i <= 900; i += 20 { + x := float64(i) + dc.DrawLine(x+50, 0, x-50, 1000) + dc.SetLineWidth(w) + dc.Stroke() + w += 0.1 + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/lorem.go b/vendor/github.com/fogleman/gg/examples/lorem.go new file mode 100644 index 0000000..07d052a --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/lorem.go @@ -0,0 +1,28 @@ +package main + +import "github.com/fogleman/gg" + +var lines = []string{ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod", + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,", + "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo", + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse", + "cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat", + "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", +} + +func main() { + const W = 800 + const H = 400 + dc := gg.NewContext(W, H) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) + // dc.LoadFontFace("/Library/Fonts/Arial.ttf", 18) + const h = 24 + for i, line := range lines { + y := H/2 - h*len(lines)/2 + i*h + dc.DrawStringAnchored(line, 400, float64(y), 0.5, 0.5) + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/mask.go b/vendor/github.com/fogleman/gg/examples/mask.go new file mode 100644 index 0000000..fb6b195 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/mask.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + + "github.com/fogleman/gg" +) + +func main() { + im, err := gg.LoadImage("examples/lenna.png") + if err != nil { + log.Fatal(err) + } + + dc := gg.NewContext(512, 512) + dc.DrawRoundedRectangle(0, 0, 512, 512, 64) + dc.Clip() + dc.DrawImage(im, 0, 0) + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/meme.go b/vendor/github.com/fogleman/gg/examples/meme.go new file mode 100644 index 0000000..cd21592 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/meme.go @@ -0,0 +1,30 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1024 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + if err := dc.LoadFontFace("/Library/Fonts/Impact.ttf", 96); err != nil { + panic(err) + } + dc.SetRGB(0, 0, 0) + s := "ONE DOES NOT SIMPLY" + n := 6 // "stroke" size + for dy := -n; dy <= n; dy++ { + for dx := -n; dx <= n; dx++ { + if dx*dx+dy*dy >= n*n { + // give it rounded corners + continue + } + x := S/2 + float64(dx) + y := S/2 + float64(dy) + dc.DrawStringAnchored(s, x, y, 0.5, 0.5) + } + } + dc.SetRGB(1, 1, 1) + dc.DrawStringAnchored(s, S/2, S/2, 0.5, 0.5) + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/openfill.go b/vendor/github.com/fogleman/gg/examples/openfill.go new file mode 100644 index 0000000..6c57d76 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/openfill.go @@ -0,0 +1,31 @@ +package main + +import ( + "math" + "math/rand" + + "github.com/fogleman/gg" +) + +func main() { + dc := gg.NewContext(1000, 1000) + for j := 0; j < 10; j++ { + for i := 0; i < 10; i++ { + x := float64(i)*100 + 50 + y := float64(j)*100 + 50 + a1 := rand.Float64() * 2 * math.Pi + a2 := a1 + rand.Float64()*math.Pi + math.Pi/2 + dc.DrawArc(x, y, 40, a1, a2) + // dc.ClosePath() + } + } + dc.SetRGB(0, 0, 0) + dc.FillPreserve() + dc.SetRGB(1, 1, 1) + dc.SetLineWidth(8) + dc.StrokePreserve() + dc.SetRGB(1, 0, 0) + dc.SetLineWidth(4) + dc.StrokePreserve() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/quadratic.go b/vendor/github.com/fogleman/gg/examples/quadratic.go new file mode 100644 index 0000000..a4bba71 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/quadratic.go @@ -0,0 +1,54 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1000 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.Translate(S/2, S/2) + dc.Scale(40, 40) + + var x0, y0, x1, y1, x2, y2, x3, y3, x4, y4 float64 + x0, y0 = -10, 0 + x1, y1 = -5, -10 + x2, y2 = 0, 0 + x3, y3 = 5, 10 + x4, y4 = 10, 0 + + dc.MoveTo(x0, y0) + dc.LineTo(x1, y1) + dc.LineTo(x2, y2) + dc.LineTo(x3, y3) + dc.LineTo(x4, y4) + dc.SetHexColor("FF2D00") + dc.SetLineWidth(8) + dc.Stroke() + + dc.MoveTo(x0, y0) + dc.QuadraticTo(x1, y1, x2, y2) + dc.QuadraticTo(x3, y3, x4, y4) + dc.SetHexColor("3E606F") + dc.SetLineWidth(16) + dc.FillPreserve() + dc.SetRGB(0, 0, 0) + dc.Stroke() + + dc.DrawCircle(x0, y0, 0.5) + dc.DrawCircle(x1, y1, 0.5) + dc.DrawCircle(x2, y2, 0.5) + dc.DrawCircle(x3, y3, 0.5) + dc.DrawCircle(x4, y4, 0.5) + dc.SetRGB(1, 1, 1) + dc.FillPreserve() + dc.SetRGB(0, 0, 0) + dc.SetLineWidth(4) + dc.Stroke() + + dc.LoadFontFace("/Library/Fonts/Arial.ttf", 200) + dc.DrawStringAnchored("g", -5, 5, 0.5, 0.5) + dc.DrawStringAnchored("G", 5, -5, 0.5, 0.5) + + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/scatter.go b/vendor/github.com/fogleman/gg/examples/scatter.go new file mode 100644 index 0000000..3dca3c0 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/scatter.go @@ -0,0 +1,66 @@ +package main + +import ( + "math/rand" + + "github.com/fogleman/gg" +) + +func CreatePoints(n int) []gg.Point { + points := make([]gg.Point, n) + for i := 0; i < n; i++ { + x := 0.5 + rand.NormFloat64()*0.1 + y := x + rand.NormFloat64()*0.1 + points[i] = gg.Point{x, y} + } + return points +} + +func main() { + const S = 1024 + const P = 64 + dc := gg.NewContext(S, S) + dc.InvertY() + dc.SetRGB(1, 1, 1) + dc.Clear() + points := CreatePoints(1000) + dc.Translate(P, P) + dc.Scale(S-P*2, S-P*2) + // draw minor grid + for i := 1; i <= 10; i++ { + x := float64(i) / 10 + dc.MoveTo(x, 0) + dc.LineTo(x, 1) + dc.MoveTo(0, x) + dc.LineTo(1, x) + } + dc.SetRGBA(0, 0, 0, 0.25) + dc.SetLineWidth(1) + dc.Stroke() + // draw axes + dc.MoveTo(0, 0) + dc.LineTo(1, 0) + dc.MoveTo(0, 0) + dc.LineTo(0, 1) + dc.SetRGB(0, 0, 0) + dc.SetLineWidth(4) + dc.Stroke() + // draw points + dc.SetRGBA(0, 0, 1, 0.5) + for _, p := range points { + dc.DrawCircle(p.X, p.Y, 3.0/S) + dc.Fill() + } + // draw text + dc.Identity() + dc.SetRGB(0, 0, 0) + if err := dc.LoadFontFace("/Library/Fonts/Arial Bold.ttf", 24); err != nil { + panic(err) + } + dc.DrawStringAnchored("Chart Title", S/2, P/2, 0.5, 0.5) + if err := dc.LoadFontFace("/Library/Fonts/Arial.ttf", 18); err != nil { + panic(err) + } + dc.DrawStringAnchored("X Axis Title", S/2, S-P/2, 0.5, 0.5) + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/sine.go b/vendor/github.com/fogleman/gg/examples/sine.go new file mode 100644 index 0000000..679da06 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/sine.go @@ -0,0 +1,29 @@ +package main + +import ( + "math" + + "github.com/fogleman/gg" +) + +func main() { + const W = 1200 + const H = 60 + dc := gg.NewContext(W, H) + // dc.SetHexColor("#FFFFFF") + // dc.Clear() + dc.ScaleAbout(0.95, 0.75, W/2, H/2) + for i := 0; i < W; i++ { + a := float64(i) * 2 * math.Pi / W * 8 + x := float64(i) + y := (math.Sin(a) + 1) / 2 * H + dc.LineTo(x, y) + } + dc.ClosePath() + dc.SetHexColor("#3E606F") + dc.FillPreserve() + dc.SetHexColor("#19344180") + dc.SetLineWidth(8) + dc.Stroke() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/spiral.go b/vendor/github.com/fogleman/gg/examples/spiral.go new file mode 100644 index 0000000..47e8ee5 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/spiral.go @@ -0,0 +1,27 @@ +package main + +import ( + "math" + + "github.com/fogleman/gg" +) + +func main() { + const S = 1024 + const N = 2048 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) + for i := 0; i <= N; i++ { + t := float64(i) / N + d := t*S*0.4 + 10 + a := t * math.Pi * 2 * 20 + x := S/2 + math.Cos(a)*d + y := S/2 + math.Sin(a)*d + r := t * 8 + dc.DrawCircle(x, y, r) + } + dc.Fill() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/star.go b/vendor/github.com/fogleman/gg/examples/star.go new file mode 100644 index 0000000..05c08d6 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/star.go @@ -0,0 +1,40 @@ +package main + +import ( + "math" + + "github.com/fogleman/gg" +) + +type Point struct { + X, Y float64 +} + +func Polygon(n int, x, y, r float64) []Point { + result := make([]Point, n) + for i := 0; i < n; i++ { + a := float64(i)*2*math.Pi/float64(n) - math.Pi/2 + result[i] = Point{x + r*math.Cos(a), y + r*math.Sin(a)} + } + return result +} + +func main() { + n := 5 + points := Polygon(n, 512, 512, 400) + dc := gg.NewContext(1024, 1024) + dc.SetHexColor("fff") + dc.Clear() + for i := 0; i < n+1; i++ { + index := (i * 2) % n + p := points[index] + dc.LineTo(p.X, p.Y) + } + dc.SetRGBA(0, 0.5, 0, 1) + dc.SetFillRule(gg.FillRuleEvenOdd) + dc.FillPreserve() + dc.SetRGBA(0, 1, 0, 0.5) + dc.SetLineWidth(16) + dc.Stroke() + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/stars.go b/vendor/github.com/fogleman/gg/examples/stars.go new file mode 100644 index 0000000..8999d12 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/stars.go @@ -0,0 +1,51 @@ +package main + +import ( + "math" + "math/rand" + + "github.com/fogleman/gg" +) + +type Point struct { + X, Y float64 +} + +func Polygon(n int) []Point { + result := make([]Point, n) + for i := 0; i < n; i++ { + a := float64(i)*2*math.Pi/float64(n) - math.Pi/2 + result[i] = Point{math.Cos(a), math.Sin(a)} + } + return result +} + +func main() { + const W = 1200 + const H = 120 + const S = 100 + dc := gg.NewContext(W, H) + dc.SetHexColor("#FFFFFF") + dc.Clear() + n := 5 + points := Polygon(n) + for x := S / 2; x < W; x += S { + dc.Push() + s := rand.Float64()*S/4 + S/4 + dc.Translate(float64(x), H/2) + dc.Rotate(rand.Float64() * 2 * math.Pi) + dc.Scale(s, s) + for i := 0; i < n+1; i++ { + index := (i * 2) % n + p := points[index] + dc.LineTo(p.X, p.Y) + } + dc.SetLineWidth(10) + dc.SetHexColor("#FFCC00") + dc.StrokePreserve() + dc.SetHexColor("#FFE43A") + dc.Fill() + dc.Pop() + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/text.go b/vendor/github.com/fogleman/gg/examples/text.go new file mode 100644 index 0000000..2b16aee --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/text.go @@ -0,0 +1,16 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1024 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) + if err := dc.LoadFontFace("/Library/Fonts/Arial.ttf", 96); err != nil { + panic(err) + } + dc.DrawStringAnchored("Hello, world!", S/2, S/2, 0.5, 0.5) + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/tiling.go b/vendor/github.com/fogleman/gg/examples/tiling.go new file mode 100644 index 0000000..9688722 --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/tiling.go @@ -0,0 +1,21 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const NX = 4 + const NY = 3 + im, err := gg.LoadPNG("examples/gopher.png") + if err != nil { + panic(err) + } + w := im.Bounds().Size().X + h := im.Bounds().Size().Y + dc := gg.NewContext(w*NX, h*NY) + for y := 0; y < NY; y++ { + for x := 0; x < NX; x++ { + dc.DrawImage(im, x*w, y*h) + } + } + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/examples/wrap.go b/vendor/github.com/fogleman/gg/examples/wrap.go new file mode 100644 index 0000000..f654e3b --- /dev/null +++ b/vendor/github.com/fogleman/gg/examples/wrap.go @@ -0,0 +1,40 @@ +package main + +import "github.com/fogleman/gg" + +const TEXT = "Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people's hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me." + +func main() { + const W = 1024 + const H = 1024 + const P = 16 + dc := gg.NewContext(W, H) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.DrawLine(W/2, 0, W/2, H) + dc.DrawLine(0, H/2, W, H/2) + dc.DrawRectangle(P, P, W-P-P, H-P-P) + dc.SetRGBA(0, 0, 1, 0.25) + dc.SetLineWidth(3) + dc.Stroke() + dc.SetRGB(0, 0, 0) + if err := dc.LoadFontFace("/Library/Fonts/Arial Bold.ttf", 18); err != nil { + panic(err) + } + dc.DrawStringWrapped("UPPER LEFT", P, P, 0, 0, 0, 1.5, gg.AlignLeft) + dc.DrawStringWrapped("UPPER RIGHT", W-P, P, 1, 0, 0, 1.5, gg.AlignRight) + dc.DrawStringWrapped("BOTTOM LEFT", P, H-P, 0, 1, 0, 1.5, gg.AlignLeft) + dc.DrawStringWrapped("BOTTOM RIGHT", W-P, H-P, 1, 1, 0, 1.5, gg.AlignRight) + dc.DrawStringWrapped("UPPER MIDDLE", W/2, P, 0.5, 0, 0, 1.5, gg.AlignCenter) + dc.DrawStringWrapped("LOWER MIDDLE", W/2, H-P, 0.5, 1, 0, 1.5, gg.AlignCenter) + dc.DrawStringWrapped("LEFT MIDDLE", P, H/2, 0, 0.5, 0, 1.5, gg.AlignLeft) + dc.DrawStringWrapped("RIGHT MIDDLE", W-P, H/2, 1, 0.5, 0, 1.5, gg.AlignRight) + if err := dc.LoadFontFace("/Library/Fonts/Arial.ttf", 12); err != nil { + panic(err) + } + dc.DrawStringWrapped(TEXT, W/2-P, H/2-P, 1, 1, W/3, 1.75, gg.AlignLeft) + dc.DrawStringWrapped(TEXT, W/2+P, H/2-P, 0, 1, W/3, 2, gg.AlignLeft) + dc.DrawStringWrapped(TEXT, W/2-P, H/2+P, 1, 0, W/3, 2.25, gg.AlignLeft) + dc.DrawStringWrapped(TEXT, W/2+P, H/2+P, 0, 0, W/3, 2.5, gg.AlignLeft) + dc.SavePNG("out.png") +} diff --git a/vendor/github.com/fogleman/gg/matrix.go b/vendor/github.com/fogleman/gg/matrix.go new file mode 100644 index 0000000..7d5b312 --- /dev/null +++ b/vendor/github.com/fogleman/gg/matrix.go @@ -0,0 +1,88 @@ +package gg + +import "math" + +type Matrix struct { + XX, YX, XY, YY, X0, Y0 float64 +} + +func Identity() Matrix { + return Matrix{ + 1, 0, + 0, 1, + 0, 0, + } +} + +func Translate(x, y float64) Matrix { + return Matrix{ + 1, 0, + 0, 1, + x, y, + } +} + +func Scale(x, y float64) Matrix { + return Matrix{ + x, 0, + 0, y, + 0, 0, + } +} + +func Rotate(angle float64) Matrix { + c := math.Cos(angle) + s := math.Sin(angle) + return Matrix{ + c, s, + -s, c, + 0, 0, + } +} + +func Shear(x, y float64) Matrix { + return Matrix{ + 1, y, + x, 1, + 0, 0, + } +} + +func (a Matrix) Multiply(b Matrix) Matrix { + return Matrix{ + a.XX*b.XX + a.YX*b.XY, + a.XX*b.YX + a.YX*b.YY, + a.XY*b.XX + a.YY*b.XY, + a.XY*b.YX + a.YY*b.YY, + a.X0*b.XX + a.Y0*b.XY + b.X0, + a.X0*b.YX + a.Y0*b.YY + b.Y0, + } +} + +func (a Matrix) TransformVector(x, y float64) (tx, ty float64) { + tx = a.XX*x + a.XY*y + ty = a.YX*x + a.YY*y + return +} + +func (a Matrix) TransformPoint(x, y float64) (tx, ty float64) { + tx = a.XX*x + a.XY*y + a.X0 + ty = a.YX*x + a.YY*y + a.Y0 + return +} + +func (a Matrix) Translate(x, y float64) Matrix { + return Translate(x, y).Multiply(a) +} + +func (a Matrix) Scale(x, y float64) Matrix { + return Scale(x, y).Multiply(a) +} + +func (a Matrix) Rotate(angle float64) Matrix { + return Rotate(angle).Multiply(a) +} + +func (a Matrix) Shear(x, y float64) Matrix { + return Shear(x, y).Multiply(a) +} diff --git a/vendor/github.com/fogleman/gg/path.go b/vendor/github.com/fogleman/gg/path.go new file mode 100644 index 0000000..74785b6 --- /dev/null +++ b/vendor/github.com/fogleman/gg/path.go @@ -0,0 +1,140 @@ +package gg + +import ( + "github.com/golang/freetype/raster" + "golang.org/x/image/math/fixed" +) + +func flattenPath(p raster.Path) [][]Point { + var result [][]Point + var path []Point + var cx, cy float64 + for i := 0; i < len(p); { + switch p[i] { + case 0: + if len(path) > 0 { + result = append(result, path) + path = nil + } + x := unfix(p[i+1]) + y := unfix(p[i+2]) + path = append(path, Point{x, y}) + cx, cy = x, y + i += 4 + case 1: + x := unfix(p[i+1]) + y := unfix(p[i+2]) + path = append(path, Point{x, y}) + cx, cy = x, y + i += 4 + case 2: + x1 := unfix(p[i+1]) + y1 := unfix(p[i+2]) + x2 := unfix(p[i+3]) + y2 := unfix(p[i+4]) + points := QuadraticBezier(cx, cy, x1, y1, x2, y2) + path = append(path, points...) + cx, cy = x2, y2 + i += 6 + case 3: + x1 := unfix(p[i+1]) + y1 := unfix(p[i+2]) + x2 := unfix(p[i+3]) + y2 := unfix(p[i+4]) + x3 := unfix(p[i+5]) + y3 := unfix(p[i+6]) + points := CubicBezier(cx, cy, x1, y1, x2, y2, x3, y3) + path = append(path, points...) + cx, cy = x3, y3 + i += 8 + default: + panic("bad path") + } + } + if len(path) > 0 { + result = append(result, path) + } + return result +} + +func dashPath(paths [][]Point, dashes []float64) [][]Point { + var result [][]Point + if len(dashes) == 0 { + return paths + } + if len(dashes) == 1 { + dashes = append(dashes, dashes[0]) + } + for _, path := range paths { + if len(path) < 2 { + continue + } + previous := path[0] + pathIndex := 1 + dashIndex := 0 + segmentLength := 0.0 + var segment []Point + segment = append(segment, previous) + for pathIndex < len(path) { + dashLength := dashes[dashIndex] + point := path[pathIndex] + d := previous.Distance(point) + maxd := dashLength - segmentLength + if d > maxd { + t := maxd / d + p := previous.Interpolate(point, t) + segment = append(segment, p) + if dashIndex%2 == 0 && len(segment) > 1 { + result = append(result, segment) + } + segment = nil + segment = append(segment, p) + segmentLength = 0 + previous = p + dashIndex = (dashIndex + 1) % len(dashes) + } else { + segment = append(segment, point) + previous = point + segmentLength += d + pathIndex++ + } + } + if dashIndex%2 == 0 && len(segment) > 1 { + result = append(result, segment) + } + } + return result +} + +func rasterPath(paths [][]Point) raster.Path { + var result raster.Path + for _, path := range paths { + var previous fixed.Point26_6 + for i, point := range path { + f := point.Fixed() + if i == 0 { + result.Start(f) + } else { + dx := f.X - previous.X + dy := f.Y - previous.Y + if dx < 0 { + dx = -dx + } + if dy < 0 { + dy = -dy + } + if dx+dy > 8 { + // TODO: this is a hack for cases where two points are + // too close - causes rendering issues with joins / caps + result.Add1(f) + } + } + previous = f + } + } + return result +} + +func dashed(path raster.Path, dashes []float64) raster.Path { + return rasterPath(dashPath(flattenPath(path), dashes)) +} diff --git a/vendor/github.com/fogleman/gg/point.go b/vendor/github.com/fogleman/gg/point.go new file mode 100644 index 0000000..d258653 --- /dev/null +++ b/vendor/github.com/fogleman/gg/point.go @@ -0,0 +1,25 @@ +package gg + +import ( + "math" + + "golang.org/x/image/math/fixed" +) + +type Point struct { + X, Y float64 +} + +func (a Point) Fixed() fixed.Point26_6 { + return fixp(a.X, a.Y) +} + +func (a Point) Distance(b Point) float64 { + return math.Hypot(a.X-b.X, a.Y-b.Y) +} + +func (a Point) Interpolate(b Point, t float64) Point { + x := a.X + (b.X-a.X)*t + y := a.Y + (b.Y-a.Y)*t + return Point{x, y} +} diff --git a/vendor/github.com/fogleman/gg/util.go b/vendor/github.com/fogleman/gg/util.go new file mode 100644 index 0000000..62577f5 --- /dev/null +++ b/vendor/github.com/fogleman/gg/util.go @@ -0,0 +1,116 @@ +package gg + +import ( + "fmt" + "image" + "image/draw" + "image/png" + "io/ioutil" + "math" + "os" + "strings" + + "github.com/golang/freetype/truetype" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +func Radians(degrees float64) float64 { + return degrees * math.Pi / 180 +} + +func Degrees(radians float64) float64 { + return radians * 180 / math.Pi +} + +func LoadImage(path string) (image.Image, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + im, _, err := image.Decode(file) + return im, err +} + +func LoadPNG(path string) (image.Image, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + return png.Decode(file) +} + +func SavePNG(path string, im image.Image) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + return png.Encode(file, im) +} + +func imageToRGBA(src image.Image) *image.RGBA { + dst := image.NewRGBA(src.Bounds()) + draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src) + return dst +} + +func parseHexColor(x string) (r, g, b, a int) { + x = strings.TrimPrefix(x, "#") + a = 255 + if len(x) == 3 { + format := "%1x%1x%1x" + fmt.Sscanf(x, format, &r, &g, &b) + r |= r << 4 + g |= g << 4 + b |= b << 4 + } + if len(x) == 6 { + format := "%02x%02x%02x" + fmt.Sscanf(x, format, &r, &g, &b) + } + if len(x) == 8 { + format := "%02x%02x%02x%02x" + fmt.Sscanf(x, format, &r, &g, &b, &a) + } + return +} + +func fixp(x, y float64) fixed.Point26_6 { + return fixed.Point26_6{fix(x), fix(y)} +} + +func fix(x float64) fixed.Int26_6 { + return fixed.Int26_6(x * 64) +} + +func unfix(x fixed.Int26_6) float64 { + const shift, mask = 6, 1<<6 - 1 + if x >= 0 { + return float64(x>>shift) + float64(x&mask)/64 + } + x = -x + if x >= 0 { + return -(float64(x>>shift) + float64(x&mask)/64) + } + return 0 +} + +func loadFontFace(path string, points float64) (font.Face, error) { + fontBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + f, err := truetype.Parse(fontBytes) + if err != nil { + return nil, err + } + face := truetype.NewFace(f, &truetype.Options{ + Size: points, + // Hinting: font.HintingFull, + }) + return face, nil +} diff --git a/vendor/github.com/fogleman/gg/wrap.go b/vendor/github.com/fogleman/gg/wrap.go new file mode 100644 index 0000000..3dfe6e1 --- /dev/null +++ b/vendor/github.com/fogleman/gg/wrap.go @@ -0,0 +1,58 @@ +package gg + +import ( + "strings" + "unicode" +) + +type measureStringer interface { + MeasureString(s string) (w, h float64) +} + +func splitOnSpace(x string) []string { + var result []string + pi := 0 + ps := false + for i, c := range x { + s := unicode.IsSpace(c) + if s != ps && i > 0 { + result = append(result, x[pi:i]) + pi = i + } + ps = s + } + result = append(result, x[pi:]) + return result +} + +func wordWrap(m measureStringer, s string, width float64) []string { + var result []string + for _, line := range strings.Split(s, "\n") { + fields := splitOnSpace(line) + if len(fields)%2 == 1 { + fields = append(fields, "") + } + x := "" + for i := 0; i < len(fields); i += 2 { + w, _ := m.MeasureString(x + fields[i]) + if w > width { + if x == "" { + result = append(result, fields[i]) + x = "" + continue + } else { + result = append(result, x) + x = "" + } + } + x += fields[i] + fields[i+1] + } + if x != "" { + result = append(result, x) + } + } + for i, line := range result { + result[i] = strings.TrimSpace(line) + } + return result +} |
