2
vendor/github.com/fogleman/gg/.gitignore
generated
vendored
Normal file
2
vendor/github.com/fogleman/gg/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.png
|
||||
|
||||
19
vendor/github.com/fogleman/gg/LICENSE.md
generated
vendored
Normal file
19
vendor/github.com/fogleman/gg/LICENSE.md
generated
vendored
Normal file
@@ -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.
|
||||
221
vendor/github.com/fogleman/gg/README.md
generated
vendored
Normal file
221
vendor/github.com/fogleman/gg/README.md
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
# Go Graphics
|
||||
|
||||
`gg` is a library for rendering 2D graphics in pure Go.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/fogleman/gg
|
||||
|
||||
Alternatively, you may use gopkg.in to grab a specific major-version:
|
||||
|
||||
go get -u gopkg.in/fogleman/gg.v1
|
||||
|
||||
## Documentation
|
||||
|
||||
https://godoc.org/github.com/fogleman/gg
|
||||
|
||||
## Hello, Circle!
|
||||
|
||||
Look how easy!
|
||||
|
||||
```go
|
||||
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")
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
There are [lots of examples](https://github.com/fogleman/gg/tree/master/examples) included. They're mostly for testing the code, but they're good for learning, too.
|
||||
|
||||

|
||||
|
||||
## Creating Contexts
|
||||
|
||||
There are a few ways of creating a context.
|
||||
|
||||
```go
|
||||
NewContext(width, height int) *Context
|
||||
NewContextForImage(im image.Image) *Context
|
||||
NewContextForRGBA(im *image.RGBA) *Context
|
||||
```
|
||||
|
||||
## Drawing Functions
|
||||
|
||||
Ever used a graphics library that didn't have functions for drawing rectangles
|
||||
or circles? What a pain!
|
||||
|
||||
```go
|
||||
DrawPoint(x, y, r float64)
|
||||
DrawLine(x1, y1, x2, y2 float64)
|
||||
DrawRectangle(x, y, w, h float64)
|
||||
DrawRoundedRectangle(x, y, w, h, r float64)
|
||||
DrawCircle(x, y, r float64)
|
||||
DrawArc(x, y, r, angle1, angle2 float64)
|
||||
DrawEllipse(x, y, rx, ry float64)
|
||||
DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64)
|
||||
DrawRegularPolygon(n int, x, y, r, rotation float64)
|
||||
DrawImage(im image.Image, x, y int)
|
||||
DrawImageAnchored(im image.Image, x, y int, ax, ay float64)
|
||||
SetPixel(x, y int)
|
||||
|
||||
MoveTo(x, y float64)
|
||||
LineTo(x, y float64)
|
||||
QuadraticTo(x1, y1, x2, y2 float64)
|
||||
CubicTo(x1, y1, x2, y2, x3, y3 float64)
|
||||
ClosePath()
|
||||
ClearPath()
|
||||
NewSubPath()
|
||||
|
||||
Clear()
|
||||
Stroke()
|
||||
Fill()
|
||||
StrokePreserve()
|
||||
FillPreserve()
|
||||
```
|
||||
|
||||
It is often desired to center an image at a point. Use `DrawImageAnchored` with `ax` and `ay` set to 0.5 to do this. Use 0 to left or top align. Use 1 to right or bottom align. `DrawStringAnchored` does the same for text, so you don't need to call `MeasureString` yourself.
|
||||
|
||||
## Text Functions
|
||||
|
||||
It will even do word wrap for you!
|
||||
|
||||
```go
|
||||
DrawString(s string, x, y float64)
|
||||
DrawStringAnchored(s string, x, y, ax, ay float64)
|
||||
DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
|
||||
MeasureString(s string) (w, h float64)
|
||||
MeasureMultilineString(s string, lineSpacing float64) (w, h float64)
|
||||
WordWrap(s string, w float64) []string
|
||||
SetFontFace(fontFace font.Face)
|
||||
LoadFontFace(path string, points float64) error
|
||||
```
|
||||
|
||||
## Color Functions
|
||||
|
||||
Colors can be set in several different ways for your convenience.
|
||||
|
||||
```go
|
||||
SetRGB(r, g, b float64)
|
||||
SetRGBA(r, g, b, a float64)
|
||||
SetRGB255(r, g, b int)
|
||||
SetRGBA255(r, g, b, a int)
|
||||
SetColor(c color.Color)
|
||||
SetHexColor(x string)
|
||||
```
|
||||
|
||||
## Stroke & Fill Options
|
||||
|
||||
```go
|
||||
SetLineWidth(lineWidth float64)
|
||||
SetLineCap(lineCap LineCap)
|
||||
SetLineJoin(lineJoin LineJoin)
|
||||
SetDash(dashes ...float64)
|
||||
SetDashOffset(offset float64)
|
||||
SetFillRule(fillRule FillRule)
|
||||
```
|
||||
|
||||
## Gradients & Patterns
|
||||
|
||||
`gg` supports linear and radial gradients and surface patterns. You can also implement your own patterns.
|
||||
|
||||
```go
|
||||
SetFillStyle(pattern Pattern)
|
||||
SetStrokeStyle(pattern Pattern)
|
||||
NewSolidPattern(color color.Color)
|
||||
NewLinearGradient(x0, y0, x1, y1 float64)
|
||||
NewRadialGradient(x0, y0, r0, x1, y1, r1 float64)
|
||||
NewSurfacePattern(im image.Image, op RepeatOp)
|
||||
```
|
||||
|
||||
## Transformation Functions
|
||||
|
||||
```go
|
||||
Identity()
|
||||
Translate(x, y float64)
|
||||
Scale(x, y float64)
|
||||
Rotate(angle float64)
|
||||
Shear(x, y float64)
|
||||
ScaleAbout(sx, sy, x, y float64)
|
||||
RotateAbout(angle, x, y float64)
|
||||
ShearAbout(sx, sy, x, y float64)
|
||||
TransformPoint(x, y float64) (tx, ty float64)
|
||||
InvertY()
|
||||
```
|
||||
|
||||
It is often desired to rotate or scale about a point that is not the origin. The functions `RotateAbout`, `ScaleAbout`, `ShearAbout` are provided as a convenience.
|
||||
|
||||
`InvertY` is provided in case Y should increase from bottom to top vs. the default top to bottom.
|
||||
|
||||
## Stack Functions
|
||||
|
||||
Save and restore the state of the context. These can be nested.
|
||||
|
||||
```go
|
||||
Push()
|
||||
Pop()
|
||||
```
|
||||
|
||||
## Clipping Functions
|
||||
|
||||
Use clipping regions to restrict drawing operations to an area that you
|
||||
defined using paths.
|
||||
|
||||
```go
|
||||
Clip()
|
||||
ClipPreserve()
|
||||
ResetClip()
|
||||
AsMask() *image.Alpha
|
||||
SetMask(mask *image.Alpha)
|
||||
InvertMask()
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
Sometimes you just don't want to write these yourself.
|
||||
|
||||
```go
|
||||
Radians(degrees float64) float64
|
||||
Degrees(radians float64) float64
|
||||
LoadImage(path string) (image.Image, error)
|
||||
LoadPNG(path string) (image.Image, error)
|
||||
SavePNG(path string, im image.Image) error
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Another Example
|
||||
|
||||
See the output of this example below.
|
||||
|
||||
```go
|
||||
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()
|
||||
}
|
||||
dc.SavePNG("out.png")
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
59
vendor/github.com/fogleman/gg/bezier.go
generated
vendored
Normal file
59
vendor/github.com/fogleman/gg/bezier.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
909
vendor/github.com/fogleman/gg/context.go
generated
vendored
Normal file
909
vendor/github.com/fogleman/gg/context.go
generated
vendored
Normal file
@@ -0,0 +1,909 @@
|
||||
// Package gg provides a simple API for rendering 2D graphics in pure Go.
|
||||
package gg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/basicfont"
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
var (
|
||||
defaultFillStyle = NewSolidPattern(color.White)
|
||||
defaultStrokeStyle = NewSolidPattern(color.Black)
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
width int
|
||||
height int
|
||||
rasterizer *raster.Rasterizer
|
||||
im *image.RGBA
|
||||
mask *image.Alpha
|
||||
color color.Color
|
||||
fillPattern Pattern
|
||||
strokePattern Pattern
|
||||
strokePath raster.Path
|
||||
fillPath raster.Path
|
||||
start Point
|
||||
current Point
|
||||
hasCurrent bool
|
||||
dashes []float64
|
||||
dashOffset 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 {
|
||||
w := im.Bounds().Size().X
|
||||
h := im.Bounds().Size().Y
|
||||
return &Context{
|
||||
width: w,
|
||||
height: h,
|
||||
rasterizer: raster.NewRasterizer(w, h),
|
||||
im: im,
|
||||
color: color.Transparent,
|
||||
fillPattern: defaultFillStyle,
|
||||
strokePattern: defaultStrokeStyle,
|
||||
lineWidth: 1,
|
||||
fillRule: FillRuleWinding,
|
||||
fontFace: basicfont.Face7x13,
|
||||
fontHeight: 13,
|
||||
matrix: Identity(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetCurrentPoint will return the current point and if there is a current point.
|
||||
// The point will have been transformed by the context's transformation matrix.
|
||||
func (dc *Context) GetCurrentPoint() (Point, bool) {
|
||||
if dc.hasCurrent {
|
||||
return dc.current, true
|
||||
}
|
||||
return Point{}, false
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SetDashOffset sets the initial offset into the dash pattern to use when
|
||||
// stroking dashed paths.
|
||||
func (dc *Context) SetDashOffset(offset float64) {
|
||||
dc.dashOffset = offset
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
func (dc *Context) setFillAndStrokeColor(c color.Color) {
|
||||
dc.color = c
|
||||
dc.fillPattern = NewSolidPattern(c)
|
||||
dc.strokePattern = NewSolidPattern(c)
|
||||
}
|
||||
|
||||
// SetFillStyle sets current fill style
|
||||
func (dc *Context) SetFillStyle(pattern Pattern) {
|
||||
// if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString)
|
||||
if fillStyle, ok := pattern.(*solidPattern); ok {
|
||||
dc.color = fillStyle.color
|
||||
}
|
||||
dc.fillPattern = pattern
|
||||
}
|
||||
|
||||
// SetStrokeStyle sets current stroke style
|
||||
func (dc *Context) SetStrokeStyle(pattern Pattern) {
|
||||
dc.strokePattern = pattern
|
||||
}
|
||||
|
||||
// SetColor sets the current color(for both fill and stroke).
|
||||
func (dc *Context) SetColor(c color.Color) {
|
||||
dc.setFillAndStrokeColor(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)}
|
||||
dc.setFillAndStrokeColor(dc.color)
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
dc.setFillAndStrokeColor(dc.color)
|
||||
}
|
||||
|
||||
// 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, dc.dashOffset)
|
||||
} else {
|
||||
// TODO: this is a temporary workaround to remove tiny segments
|
||||
// that result in rendering issues
|
||||
path = rasterPath(flattenPath(path))
|
||||
}
|
||||
r := dc.rasterizer
|
||||
r.UseNonZeroWinding = true
|
||||
r.Clear()
|
||||
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 := dc.rasterizer
|
||||
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
|
||||
r.Clear()
|
||||
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() {
|
||||
var painter raster.Painter
|
||||
if dc.mask == nil {
|
||||
if pattern, ok := dc.strokePattern.(*solidPattern); ok {
|
||||
// with a nil mask and a solid color pattern, we can be more efficient
|
||||
// TODO: refactor so we don't have to do this type assertion stuff?
|
||||
p := raster.NewRGBAPainter(dc.im)
|
||||
p.SetColor(pattern.color)
|
||||
painter = p
|
||||
}
|
||||
}
|
||||
if painter == nil {
|
||||
painter = newPatternPainter(dc.im, dc.mask, dc.strokePattern)
|
||||
}
|
||||
dc.stroke(painter)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
var painter raster.Painter
|
||||
if dc.mask == nil {
|
||||
if pattern, ok := dc.fillPattern.(*solidPattern); ok {
|
||||
// with a nil mask and a solid color pattern, we can be more efficient
|
||||
// TODO: refactor so we don't have to do this type assertion stuff?
|
||||
p := raster.NewRGBAPainter(dc.im)
|
||||
p.SetColor(pattern.color)
|
||||
painter = p
|
||||
}
|
||||
}
|
||||
if painter == nil {
|
||||
painter = newPatternPainter(dc.im, dc.mask, dc.fillPattern)
|
||||
}
|
||||
dc.fill(painter)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// SetMask allows you to directly set the *image.Alpha to be used as a clipping
|
||||
// mask. It must be the same size as the context, else an error is returned
|
||||
// and the mask is unchanged.
|
||||
func (dc *Context) SetMask(mask *image.Alpha) error {
|
||||
if mask.Bounds().Size() != dc.im.Bounds().Size() {
|
||||
return errors.New("mask size must match context size")
|
||||
}
|
||||
dc.mask = mask
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsMask returns an *image.Alpha representing the alpha channel of this
|
||||
// context. This can be useful for advanced clipping operations where you first
|
||||
// render the mask geometry and then use it as a mask.
|
||||
func (dc *Context) AsMask() *image.Alpha {
|
||||
mask := image.NewAlpha(dc.im.Bounds())
|
||||
draw.Draw(mask, dc.im.Bounds(), dc.im, image.ZP, draw.Src)
|
||||
return mask
|
||||
}
|
||||
|
||||
// InvertMask inverts the alpha values in the current clipping mask such that
|
||||
// a fully transparent region becomes fully opaque and vice versa.
|
||||
func (dc *Context) InvertMask() {
|
||||
if dc.mask == nil {
|
||||
dc.mask = image.NewAlpha(dc.im.Bounds())
|
||||
} else {
|
||||
for i, a := range dc.mask.Pix {
|
||||
dc.mask.Pix[i] = 255 - a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// SetPixel sets the color of the specified pixel using the current color.
|
||||
func (dc *Context) SetPixel(x, y int) {
|
||||
dc.im.Set(x, y, dc.color)
|
||||
}
|
||||
|
||||
// DrawPoint is like DrawCircle but ensures that a circle of the specified
|
||||
// size is drawn regardless of the current transformation matrix. The position
|
||||
// is still transformed, but not the shape of the point.
|
||||
func (dc *Context) DrawPoint(x, y, r float64) {
|
||||
dc.Push()
|
||||
tx, ty := dc.TransformPoint(x, y)
|
||||
dc.Identity()
|
||||
dc.DrawCircle(tx, ty, r)
|
||||
dc.Pop()
|
||||
}
|
||||
|
||||
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)/2)
|
||||
y1 := y + ry*math.Sin((a1+a2)/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 {
|
||||
if dc.hasCurrent {
|
||||
dc.LineTo(x0, y0)
|
||||
} else {
|
||||
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.
|
||||
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))
|
||||
transformer := draw.BiLinear
|
||||
fx, fy := float64(x), float64(y)
|
||||
m := dc.matrix.Translate(fx, fy)
|
||||
s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
||||
if dc.mask == nil {
|
||||
transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, nil)
|
||||
} else {
|
||||
transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, &draw.Options{
|
||||
DstMask: dc.mask,
|
||||
DstMaskP: image.ZP,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Text Functions
|
||||
|
||||
func (dc *Context) SetFontFace(fontFace font.Face) {
|
||||
dc.fontFace = fontFace
|
||||
dc.fontHeight = float64(fontFace.Metrics().Height) / 64
|
||||
}
|
||||
|
||||
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) FontHeight() float64 {
|
||||
return dc.fontHeight
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
// based on Drawer.DrawString() in golang.org/x/image/font/font.go
|
||||
prevC := rune(-1)
|
||||
for _, c := range s {
|
||||
if prevC >= 0 {
|
||||
d.Dot.X += d.Face.Kern(prevC, c)
|
||||
}
|
||||
dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
|
||||
if !ok {
|
||||
// TODO: is falling back on the U+FFFD glyph the responsibility of
|
||||
// the Drawer or the Face?
|
||||
// TODO: set prevC = '\ufffd'?
|
||||
continue
|
||||
}
|
||||
sr := dr.Sub(dr.Min)
|
||||
transformer := draw.BiLinear
|
||||
fx, fy := float64(dr.Min.X), float64(dr.Min.Y)
|
||||
m := dc.matrix.Translate(fx, fy)
|
||||
s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
||||
transformer.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{
|
||||
SrcMask: mask,
|
||||
SrcMaskP: maskp,
|
||||
})
|
||||
d.Dot.X += advance
|
||||
prevC = c
|
||||
}
|
||||
}
|
||||
|
||||
// DrawString draws the specified text at the specified point.
|
||||
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 -= 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)
|
||||
|
||||
// sync h formula with MeasureMultilineString
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *Context) MeasureMultilineString(s string, lineSpacing float64) (width, height float64) {
|
||||
lines := strings.Split(s, "\n")
|
||||
|
||||
// sync h formula with DrawStringWrapped
|
||||
height = float64(len(lines)) * dc.fontHeight * lineSpacing
|
||||
height -= (lineSpacing - 1) * dc.fontHeight
|
||||
|
||||
d := &font.Drawer{
|
||||
Face: dc.fontFace,
|
||||
}
|
||||
|
||||
// max width from lines
|
||||
for _, line := range lines {
|
||||
adv := d.MeasureString(line)
|
||||
currentWidth := float64(adv >> 6) // from gg.Context.MeasureString
|
||||
if currentWidth > width {
|
||||
width = currentWidth
|
||||
}
|
||||
}
|
||||
|
||||
return width, height
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
202
vendor/github.com/fogleman/gg/gradient.go
generated
vendored
Normal file
202
vendor/github.com/fogleman/gg/gradient.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
package gg
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type stop struct {
|
||||
pos float64
|
||||
color color.Color
|
||||
}
|
||||
|
||||
type stops []stop
|
||||
|
||||
// Len satisfies the Sort interface.
|
||||
func (s stops) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Less satisfies the Sort interface.
|
||||
func (s stops) Less(i, j int) bool {
|
||||
return s[i].pos < s[j].pos
|
||||
}
|
||||
|
||||
// Swap satisfies the Sort interface.
|
||||
func (s stops) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
type Gradient interface {
|
||||
Pattern
|
||||
AddColorStop(offset float64, color color.Color)
|
||||
}
|
||||
|
||||
// Linear Gradient
|
||||
type linearGradient struct {
|
||||
x0, y0, x1, y1 float64
|
||||
stops stops
|
||||
}
|
||||
|
||||
func (g *linearGradient) ColorAt(x, y int) color.Color {
|
||||
if len(g.stops) == 0 {
|
||||
return color.Transparent
|
||||
}
|
||||
|
||||
fx, fy := float64(x), float64(y)
|
||||
x0, y0, x1, y1 := g.x0, g.y0, g.x1, g.y1
|
||||
dx, dy := x1-x0, y1-y0
|
||||
|
||||
// Horizontal
|
||||
if dy == 0 && dx != 0 {
|
||||
return getColor((fx-x0)/dx, g.stops)
|
||||
}
|
||||
|
||||
// Vertical
|
||||
if dx == 0 && dy != 0 {
|
||||
return getColor((fy-y0)/dy, g.stops)
|
||||
}
|
||||
|
||||
// Dot product
|
||||
s0 := dx*(fx-x0) + dy*(fy-y0)
|
||||
if s0 < 0 {
|
||||
return g.stops[0].color
|
||||
}
|
||||
// Calculate distance to (x0,y0) alone (x0,y0)->(x1,y1)
|
||||
mag := math.Hypot(dx, dy)
|
||||
u := ((fx-x0)*-dy + (fy-y0)*dx) / (mag * mag)
|
||||
x2, y2 := x0+u*-dy, y0+u*dx
|
||||
d := math.Hypot(fx-x2, fy-y2) / mag
|
||||
return getColor(d, g.stops)
|
||||
}
|
||||
|
||||
func (g *linearGradient) AddColorStop(offset float64, color color.Color) {
|
||||
g.stops = append(g.stops, stop{pos: offset, color: color})
|
||||
sort.Sort(g.stops)
|
||||
}
|
||||
|
||||
func NewLinearGradient(x0, y0, x1, y1 float64) Gradient {
|
||||
g := &linearGradient{
|
||||
x0: x0, y0: y0,
|
||||
x1: x1, y1: y1,
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// Radial Gradient
|
||||
type circle struct {
|
||||
x, y, r float64
|
||||
}
|
||||
|
||||
type radialGradient struct {
|
||||
c0, c1, cd circle
|
||||
a, inva float64
|
||||
mindr float64
|
||||
stops stops
|
||||
}
|
||||
|
||||
func dot3(x0, y0, z0, x1, y1, z1 float64) float64 {
|
||||
return x0*x1 + y0*y1 + z0*z1
|
||||
}
|
||||
|
||||
func (g *radialGradient) ColorAt(x, y int) color.Color {
|
||||
if len(g.stops) == 0 {
|
||||
return color.Transparent
|
||||
}
|
||||
|
||||
// copy from pixman's pixman-radial-gradient.c
|
||||
|
||||
dx, dy := float64(x)+0.5-g.c0.x, float64(y)+0.5-g.c0.y
|
||||
b := dot3(dx, dy, g.c0.r, g.cd.x, g.cd.y, g.cd.r)
|
||||
c := dot3(dx, dy, -g.c0.r, dx, dy, g.c0.r)
|
||||
|
||||
if g.a == 0 {
|
||||
if b == 0 {
|
||||
return color.Transparent
|
||||
}
|
||||
t := 0.5 * c / b
|
||||
if t*g.cd.r >= g.mindr {
|
||||
return getColor(t, g.stops)
|
||||
}
|
||||
return color.Transparent
|
||||
}
|
||||
|
||||
discr := dot3(b, g.a, 0, b, -c, 0)
|
||||
if discr >= 0 {
|
||||
sqrtdiscr := math.Sqrt(discr)
|
||||
t0 := (b + sqrtdiscr) * g.inva
|
||||
t1 := (b - sqrtdiscr) * g.inva
|
||||
|
||||
if t0*g.cd.r >= g.mindr {
|
||||
return getColor(t0, g.stops)
|
||||
} else if t1*g.cd.r >= g.mindr {
|
||||
return getColor(t1, g.stops)
|
||||
}
|
||||
}
|
||||
|
||||
return color.Transparent
|
||||
}
|
||||
|
||||
func (g *radialGradient) AddColorStop(offset float64, color color.Color) {
|
||||
g.stops = append(g.stops, stop{pos: offset, color: color})
|
||||
sort.Sort(g.stops)
|
||||
}
|
||||
|
||||
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient {
|
||||
c0 := circle{x0, y0, r0}
|
||||
c1 := circle{x1, y1, r1}
|
||||
cd := circle{x1 - x0, y1 - y0, r1 - r0}
|
||||
a := dot3(cd.x, cd.y, -cd.r, cd.x, cd.y, cd.r)
|
||||
var inva float64
|
||||
if a != 0 {
|
||||
inva = 1.0 / a
|
||||
}
|
||||
mindr := -c0.r
|
||||
g := &radialGradient{
|
||||
c0: c0,
|
||||
c1: c1,
|
||||
cd: cd,
|
||||
a: a,
|
||||
inva: inva,
|
||||
mindr: mindr,
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func getColor(pos float64, stops stops) color.Color {
|
||||
if pos <= 0.0 || len(stops) == 1 {
|
||||
return stops[0].color
|
||||
}
|
||||
|
||||
last := stops[len(stops)-1]
|
||||
|
||||
if pos >= last.pos {
|
||||
return last.color
|
||||
}
|
||||
|
||||
for i, stop := range stops[1:] {
|
||||
if pos < stop.pos {
|
||||
pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos)
|
||||
return colorLerp(stops[i].color, stop.color, pos)
|
||||
}
|
||||
}
|
||||
|
||||
return last.color
|
||||
}
|
||||
|
||||
func colorLerp(c0, c1 color.Color, t float64) color.Color {
|
||||
r0, g0, b0, a0 := c0.RGBA()
|
||||
r1, g1, b1, a1 := c1.RGBA()
|
||||
|
||||
return color.RGBA{
|
||||
lerp(r0, r1, t),
|
||||
lerp(g0, g1, t),
|
||||
lerp(b0, b1, t),
|
||||
lerp(a0, a1, t),
|
||||
}
|
||||
}
|
||||
|
||||
func lerp(a, b uint32, t float64) uint8 {
|
||||
return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8)
|
||||
}
|
||||
88
vendor/github.com/fogleman/gg/matrix.go
generated
vendored
Normal file
88
vendor/github.com/fogleman/gg/matrix.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
163
vendor/github.com/fogleman/gg/path.go
generated
vendored
Normal file
163
vendor/github.com/fogleman/gg/path.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
package gg
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"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, offset 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
|
||||
|
||||
// offset
|
||||
if offset != 0 {
|
||||
var totalLength float64
|
||||
for _, dashLength := range dashes {
|
||||
totalLength += dashLength
|
||||
}
|
||||
offset = math.Mod(offset, totalLength)
|
||||
if offset < 0 {
|
||||
offset += totalLength
|
||||
}
|
||||
for i, dashLength := range dashes {
|
||||
offset -= dashLength
|
||||
if offset < 0 {
|
||||
dashIndex = i
|
||||
segmentLength = dashLength + offset
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, offset float64) raster.Path {
|
||||
return rasterPath(dashPath(flattenPath(path), dashes, offset))
|
||||
}
|
||||
123
vendor/github.com/fogleman/gg/pattern.go
generated
vendored
Normal file
123
vendor/github.com/fogleman/gg/pattern.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
package gg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
)
|
||||
|
||||
type RepeatOp int
|
||||
|
||||
const (
|
||||
RepeatBoth RepeatOp = iota
|
||||
RepeatX
|
||||
RepeatY
|
||||
RepeatNone
|
||||
)
|
||||
|
||||
type Pattern interface {
|
||||
ColorAt(x, y int) color.Color
|
||||
}
|
||||
|
||||
// Solid Pattern
|
||||
type solidPattern struct {
|
||||
color color.Color
|
||||
}
|
||||
|
||||
func (p *solidPattern) ColorAt(x, y int) color.Color {
|
||||
return p.color
|
||||
}
|
||||
|
||||
func NewSolidPattern(color color.Color) Pattern {
|
||||
return &solidPattern{color: color}
|
||||
}
|
||||
|
||||
// Surface Pattern
|
||||
type surfacePattern struct {
|
||||
im image.Image
|
||||
op RepeatOp
|
||||
}
|
||||
|
||||
func (p *surfacePattern) ColorAt(x, y int) color.Color {
|
||||
b := p.im.Bounds()
|
||||
switch p.op {
|
||||
case RepeatX:
|
||||
if y >= b.Dy() {
|
||||
return color.Transparent
|
||||
}
|
||||
case RepeatY:
|
||||
if x >= b.Dx() {
|
||||
return color.Transparent
|
||||
}
|
||||
case RepeatNone:
|
||||
if x >= b.Dx() || y >= b.Dy() {
|
||||
return color.Transparent
|
||||
}
|
||||
}
|
||||
x = x%b.Dx() + b.Min.X
|
||||
y = y%b.Dy() + b.Min.Y
|
||||
return p.im.At(x, y)
|
||||
}
|
||||
|
||||
func NewSurfacePattern(im image.Image, op RepeatOp) Pattern {
|
||||
return &surfacePattern{im: im, op: op}
|
||||
}
|
||||
|
||||
type patternPainter struct {
|
||||
im *image.RGBA
|
||||
mask *image.Alpha
|
||||
p Pattern
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface.
|
||||
func (r *patternPainter) Paint(ss []raster.Span, done bool) {
|
||||
b := r.im.Bounds()
|
||||
for _, s := range ss {
|
||||
if s.Y < b.Min.Y {
|
||||
continue
|
||||
}
|
||||
if s.Y >= b.Max.Y {
|
||||
return
|
||||
}
|
||||
if s.X0 < b.Min.X {
|
||||
s.X0 = b.Min.X
|
||||
}
|
||||
if s.X1 > b.Max.X {
|
||||
s.X1 = b.Max.X
|
||||
}
|
||||
if s.X0 >= s.X1 {
|
||||
continue
|
||||
}
|
||||
const m = 1<<16 - 1
|
||||
y := s.Y - r.im.Rect.Min.Y
|
||||
x0 := s.X0 - r.im.Rect.Min.X
|
||||
// RGBAPainter.Paint() in $GOPATH/src/github.com/golang/freetype/raster/paint.go
|
||||
i0 := (s.Y-r.im.Rect.Min.Y)*r.im.Stride + (s.X0-r.im.Rect.Min.X)*4
|
||||
i1 := i0 + (s.X1-s.X0)*4
|
||||
for i, x := i0, x0; i < i1; i, x = i+4, x+1 {
|
||||
ma := s.Alpha
|
||||
if r.mask != nil {
|
||||
ma = ma * uint32(r.mask.AlphaAt(x, y).A) / 255
|
||||
if ma == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
c := r.p.ColorAt(x, y)
|
||||
cr, cg, cb, ca := c.RGBA()
|
||||
dr := uint32(r.im.Pix[i+0])
|
||||
dg := uint32(r.im.Pix[i+1])
|
||||
db := uint32(r.im.Pix[i+2])
|
||||
da := uint32(r.im.Pix[i+3])
|
||||
a := (m - (ca * ma / m)) * 0x101
|
||||
r.im.Pix[i+0] = uint8((dr*a + cr*ma) / m >> 8)
|
||||
r.im.Pix[i+1] = uint8((dg*a + cg*ma) / m >> 8)
|
||||
r.im.Pix[i+2] = uint8((db*a + cb*ma) / m >> 8)
|
||||
r.im.Pix[i+3] = uint8((da*a + ca*ma) / m >> 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newPatternPainter(im *image.RGBA, mask *image.Alpha, p Pattern) *patternPainter {
|
||||
return &patternPainter{im, mask, p}
|
||||
}
|
||||
25
vendor/github.com/fogleman/gg/point.go
generated
vendored
Normal file
25
vendor/github.com/fogleman/gg/point.go
generated
vendored
Normal file
@@ -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}
|
||||
}
|
||||
146
vendor/github.com/fogleman/gg/util.go
generated
vendored
Normal file
146
vendor/github.com/fogleman/gg/util.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
package gg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
"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 LoadJPG(path string) (image.Image, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return jpeg.Decode(file)
|
||||
}
|
||||
|
||||
func SaveJPG(path string, im image.Image, quality int) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var opt jpeg.Options
|
||||
opt.Quality = quality
|
||||
|
||||
return jpeg.Encode(file, im, &opt)
|
||||
}
|
||||
|
||||
func imageToRGBA(src image.Image) *image.RGBA {
|
||||
bounds := src.Bounds()
|
||||
dst := image.NewRGBA(bounds)
|
||||
draw.Draw(dst, bounds, src, bounds.Min, 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
|
||||
}
|
||||
|
||||
// LoadFontFace is a helper function to load the specified font file with
|
||||
// the specified point size. Note that the returned `font.Face` objects
|
||||
// are not thread safe and cannot be used in parallel across goroutines.
|
||||
// You can usually just use the Context.LoadFontFace function instead of
|
||||
// this package-level function.
|
||||
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
|
||||
}
|
||||
58
vendor/github.com/fogleman/gg/wrap.go
generated
vendored
Normal file
58
vendor/github.com/fogleman/gg/wrap.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user