context.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. // Package gg provides a simple API for rendering 2D graphics in pure Go.
  2. package gg
  3. import (
  4. "errors"
  5. "image"
  6. "image/color"
  7. "image/png"
  8. "io"
  9. "math"
  10. "strings"
  11. "github.com/golang/freetype/raster"
  12. "golang.org/x/image/draw"
  13. "golang.org/x/image/font"
  14. "golang.org/x/image/font/basicfont"
  15. "golang.org/x/image/math/f64"
  16. )
  17. type LineCap int
  18. const (
  19. LineCapRound LineCap = iota
  20. LineCapButt
  21. LineCapSquare
  22. )
  23. type LineJoin int
  24. const (
  25. LineJoinRound LineJoin = iota
  26. LineJoinBevel
  27. )
  28. type FillRule int
  29. const (
  30. FillRuleWinding FillRule = iota
  31. FillRuleEvenOdd
  32. )
  33. type Align int
  34. const (
  35. AlignLeft Align = iota
  36. AlignCenter
  37. AlignRight
  38. )
  39. var (
  40. defaultFillStyle = NewSolidPattern(color.White)
  41. defaultStrokeStyle = NewSolidPattern(color.Black)
  42. )
  43. type Context struct {
  44. width int
  45. height int
  46. rasterizer *raster.Rasterizer
  47. im *image.RGBA
  48. mask *image.Alpha
  49. color color.Color
  50. fillPattern Pattern
  51. strokePattern Pattern
  52. strokePath raster.Path
  53. fillPath raster.Path
  54. start Point
  55. current Point
  56. hasCurrent bool
  57. dashes []float64
  58. dashOffset float64
  59. lineWidth float64
  60. lineCap LineCap
  61. lineJoin LineJoin
  62. fillRule FillRule
  63. fontFace font.Face
  64. fontHeight float64
  65. matrix Matrix
  66. stack []*Context
  67. }
  68. // NewContext creates a new image.RGBA with the specified width and height
  69. // and prepares a context for rendering onto that image.
  70. func NewContext(width, height int) *Context {
  71. return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height)))
  72. }
  73. // NewContextForImage copies the specified image into a new image.RGBA
  74. // and prepares a context for rendering onto that image.
  75. func NewContextForImage(im image.Image) *Context {
  76. return NewContextForRGBA(imageToRGBA(im))
  77. }
  78. // NewContextForRGBA prepares a context for rendering onto the specified image.
  79. // No copy is made.
  80. func NewContextForRGBA(im *image.RGBA) *Context {
  81. w := im.Bounds().Size().X
  82. h := im.Bounds().Size().Y
  83. return &Context{
  84. width: w,
  85. height: h,
  86. rasterizer: raster.NewRasterizer(w, h),
  87. im: im,
  88. color: color.Transparent,
  89. fillPattern: defaultFillStyle,
  90. strokePattern: defaultStrokeStyle,
  91. lineWidth: 1,
  92. fillRule: FillRuleWinding,
  93. fontFace: basicfont.Face7x13,
  94. fontHeight: 13,
  95. matrix: Identity(),
  96. }
  97. }
  98. // GetCurrentPoint will return the current point and if there is a current point.
  99. // The point will have been transformed by the context's transformation matrix.
  100. func (dc *Context) GetCurrentPoint() (Point, bool) {
  101. if dc.hasCurrent {
  102. return dc.current, true
  103. }
  104. return Point{}, false
  105. }
  106. // Image returns the image that has been drawn by this context.
  107. func (dc *Context) Image() image.Image {
  108. return dc.im
  109. }
  110. // Width returns the width of the image in pixels.
  111. func (dc *Context) Width() int {
  112. return dc.width
  113. }
  114. // Height returns the height of the image in pixels.
  115. func (dc *Context) Height() int {
  116. return dc.height
  117. }
  118. // SavePNG encodes the image as a PNG and writes it to disk.
  119. func (dc *Context) SavePNG(path string) error {
  120. return SavePNG(path, dc.im)
  121. }
  122. // EncodePNG encodes the image as a PNG and writes it to the provided io.Writer.
  123. func (dc *Context) EncodePNG(w io.Writer) error {
  124. return png.Encode(w, dc.im)
  125. }
  126. // SetDash sets the current dash pattern to use. Call with zero arguments to
  127. // disable dashes. The values specify the lengths of each dash, with
  128. // alternating on and off lengths.
  129. func (dc *Context) SetDash(dashes ...float64) {
  130. dc.dashes = dashes
  131. }
  132. // SetDashOffset sets the initial offset into the dash pattern to use when
  133. // stroking dashed paths.
  134. func (dc *Context) SetDashOffset(offset float64) {
  135. dc.dashOffset = offset
  136. }
  137. func (dc *Context) SetLineWidth(lineWidth float64) {
  138. dc.lineWidth = lineWidth
  139. }
  140. func (dc *Context) SetLineCap(lineCap LineCap) {
  141. dc.lineCap = lineCap
  142. }
  143. func (dc *Context) SetLineCapRound() {
  144. dc.lineCap = LineCapRound
  145. }
  146. func (dc *Context) SetLineCapButt() {
  147. dc.lineCap = LineCapButt
  148. }
  149. func (dc *Context) SetLineCapSquare() {
  150. dc.lineCap = LineCapSquare
  151. }
  152. func (dc *Context) SetLineJoin(lineJoin LineJoin) {
  153. dc.lineJoin = lineJoin
  154. }
  155. func (dc *Context) SetLineJoinRound() {
  156. dc.lineJoin = LineJoinRound
  157. }
  158. func (dc *Context) SetLineJoinBevel() {
  159. dc.lineJoin = LineJoinBevel
  160. }
  161. func (dc *Context) SetFillRule(fillRule FillRule) {
  162. dc.fillRule = fillRule
  163. }
  164. func (dc *Context) SetFillRuleWinding() {
  165. dc.fillRule = FillRuleWinding
  166. }
  167. func (dc *Context) SetFillRuleEvenOdd() {
  168. dc.fillRule = FillRuleEvenOdd
  169. }
  170. // Color Setters
  171. func (dc *Context) setFillAndStrokeColor(c color.Color) {
  172. dc.color = c
  173. dc.fillPattern = NewSolidPattern(c)
  174. dc.strokePattern = NewSolidPattern(c)
  175. }
  176. // SetFillStyle sets current fill style
  177. func (dc *Context) SetFillStyle(pattern Pattern) {
  178. // if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString)
  179. if fillStyle, ok := pattern.(*solidPattern); ok {
  180. dc.color = fillStyle.color
  181. }
  182. dc.fillPattern = pattern
  183. }
  184. // SetStrokeStyle sets current stroke style
  185. func (dc *Context) SetStrokeStyle(pattern Pattern) {
  186. dc.strokePattern = pattern
  187. }
  188. // SetColor sets the current color(for both fill and stroke).
  189. func (dc *Context) SetColor(c color.Color) {
  190. dc.setFillAndStrokeColor(c)
  191. }
  192. // SetHexColor sets the current color using a hex string. The leading pound
  193. // sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits
  194. // may be provided to set the alpha value as well.
  195. func (dc *Context) SetHexColor(x string) {
  196. r, g, b, a := parseHexColor(x)
  197. dc.SetRGBA255(r, g, b, a)
  198. }
  199. // SetRGBA255 sets the current color. r, g, b, a values should be between 0 and
  200. // 255, inclusive.
  201. func (dc *Context) SetRGBA255(r, g, b, a int) {
  202. dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
  203. dc.setFillAndStrokeColor(dc.color)
  204. }
  205. // SetRGB255 sets the current color. r, g, b values should be between 0 and 255,
  206. // inclusive. Alpha will be set to 255 (fully opaque).
  207. func (dc *Context) SetRGB255(r, g, b int) {
  208. dc.SetRGBA255(r, g, b, 255)
  209. }
  210. // SetRGBA sets the current color. r, g, b, a values should be between 0 and 1,
  211. // inclusive.
  212. func (dc *Context) SetRGBA(r, g, b, a float64) {
  213. dc.color = color.NRGBA{
  214. uint8(r * 255),
  215. uint8(g * 255),
  216. uint8(b * 255),
  217. uint8(a * 255),
  218. }
  219. dc.setFillAndStrokeColor(dc.color)
  220. }
  221. // SetRGB sets the current color. r, g, b values should be between 0 and 1,
  222. // inclusive. Alpha will be set to 1 (fully opaque).
  223. func (dc *Context) SetRGB(r, g, b float64) {
  224. dc.SetRGBA(r, g, b, 1)
  225. }
  226. // Path Manipulation
  227. // MoveTo starts a new subpath within the current path starting at the
  228. // specified point.
  229. func (dc *Context) MoveTo(x, y float64) {
  230. if dc.hasCurrent {
  231. dc.fillPath.Add1(dc.start.Fixed())
  232. }
  233. x, y = dc.TransformPoint(x, y)
  234. p := Point{x, y}
  235. dc.strokePath.Start(p.Fixed())
  236. dc.fillPath.Start(p.Fixed())
  237. dc.start = p
  238. dc.current = p
  239. dc.hasCurrent = true
  240. }
  241. // LineTo adds a line segment to the current path starting at the current
  242. // point. If there is no current point, it is equivalent to MoveTo(x, y)
  243. func (dc *Context) LineTo(x, y float64) {
  244. if !dc.hasCurrent {
  245. dc.MoveTo(x, y)
  246. } else {
  247. x, y = dc.TransformPoint(x, y)
  248. p := Point{x, y}
  249. dc.strokePath.Add1(p.Fixed())
  250. dc.fillPath.Add1(p.Fixed())
  251. dc.current = p
  252. }
  253. }
  254. // QuadraticTo adds a quadratic bezier curve to the current path starting at
  255. // the current point. If there is no current point, it first performs
  256. // MoveTo(x1, y1)
  257. func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) {
  258. if !dc.hasCurrent {
  259. dc.MoveTo(x1, y1)
  260. }
  261. x1, y1 = dc.TransformPoint(x1, y1)
  262. x2, y2 = dc.TransformPoint(x2, y2)
  263. p1 := Point{x1, y1}
  264. p2 := Point{x2, y2}
  265. dc.strokePath.Add2(p1.Fixed(), p2.Fixed())
  266. dc.fillPath.Add2(p1.Fixed(), p2.Fixed())
  267. dc.current = p2
  268. }
  269. // CubicTo adds a cubic bezier curve to the current path starting at the
  270. // current point. If there is no current point, it first performs
  271. // MoveTo(x1, y1). Because freetype/raster does not support cubic beziers,
  272. // this is emulated with many small line segments.
  273. func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) {
  274. if !dc.hasCurrent {
  275. dc.MoveTo(x1, y1)
  276. }
  277. x0, y0 := dc.current.X, dc.current.Y
  278. x1, y1 = dc.TransformPoint(x1, y1)
  279. x2, y2 = dc.TransformPoint(x2, y2)
  280. x3, y3 = dc.TransformPoint(x3, y3)
  281. points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3)
  282. previous := dc.current.Fixed()
  283. for _, p := range points[1:] {
  284. f := p.Fixed()
  285. if f == previous {
  286. // TODO: this fixes some rendering issues but not all
  287. continue
  288. }
  289. previous = f
  290. dc.strokePath.Add1(f)
  291. dc.fillPath.Add1(f)
  292. dc.current = p
  293. }
  294. }
  295. // ClosePath adds a line segment from the current point to the beginning
  296. // of the current subpath. If there is no current point, this is a no-op.
  297. func (dc *Context) ClosePath() {
  298. if dc.hasCurrent {
  299. dc.strokePath.Add1(dc.start.Fixed())
  300. dc.fillPath.Add1(dc.start.Fixed())
  301. dc.current = dc.start
  302. }
  303. }
  304. // ClearPath clears the current path. There is no current point after this
  305. // operation.
  306. func (dc *Context) ClearPath() {
  307. dc.strokePath.Clear()
  308. dc.fillPath.Clear()
  309. dc.hasCurrent = false
  310. }
  311. // NewSubPath starts a new subpath within the current path. There is no current
  312. // point after this operation.
  313. func (dc *Context) NewSubPath() {
  314. if dc.hasCurrent {
  315. dc.fillPath.Add1(dc.start.Fixed())
  316. }
  317. dc.hasCurrent = false
  318. }
  319. // Path Drawing
  320. func (dc *Context) capper() raster.Capper {
  321. switch dc.lineCap {
  322. case LineCapButt:
  323. return raster.ButtCapper
  324. case LineCapRound:
  325. return raster.RoundCapper
  326. case LineCapSquare:
  327. return raster.SquareCapper
  328. }
  329. return nil
  330. }
  331. func (dc *Context) joiner() raster.Joiner {
  332. switch dc.lineJoin {
  333. case LineJoinBevel:
  334. return raster.BevelJoiner
  335. case LineJoinRound:
  336. return raster.RoundJoiner
  337. }
  338. return nil
  339. }
  340. func (dc *Context) stroke(painter raster.Painter) {
  341. path := dc.strokePath
  342. if len(dc.dashes) > 0 {
  343. path = dashed(path, dc.dashes, dc.dashOffset)
  344. } else {
  345. // TODO: this is a temporary workaround to remove tiny segments
  346. // that result in rendering issues
  347. path = rasterPath(flattenPath(path))
  348. }
  349. r := dc.rasterizer
  350. r.UseNonZeroWinding = true
  351. r.Clear()
  352. r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
  353. r.Rasterize(painter)
  354. }
  355. func (dc *Context) fill(painter raster.Painter) {
  356. path := dc.fillPath
  357. if dc.hasCurrent {
  358. path = make(raster.Path, len(dc.fillPath))
  359. copy(path, dc.fillPath)
  360. path.Add1(dc.start.Fixed())
  361. }
  362. r := dc.rasterizer
  363. r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
  364. r.Clear()
  365. r.AddPath(path)
  366. r.Rasterize(painter)
  367. }
  368. // StrokePreserve strokes the current path with the current color, line width,
  369. // line cap, line join and dash settings. The path is preserved after this
  370. // operation.
  371. func (dc *Context) StrokePreserve() {
  372. var painter raster.Painter
  373. if dc.mask == nil {
  374. if pattern, ok := dc.strokePattern.(*solidPattern); ok {
  375. // with a nil mask and a solid color pattern, we can be more efficient
  376. // TODO: refactor so we don't have to do this type assertion stuff?
  377. p := raster.NewRGBAPainter(dc.im)
  378. p.SetColor(pattern.color)
  379. painter = p
  380. }
  381. }
  382. if painter == nil {
  383. painter = newPatternPainter(dc.im, dc.mask, dc.strokePattern)
  384. }
  385. dc.stroke(painter)
  386. }
  387. // Stroke strokes the current path with the current color, line width,
  388. // line cap, line join and dash settings. The path is cleared after this
  389. // operation.
  390. func (dc *Context) Stroke() {
  391. dc.StrokePreserve()
  392. dc.ClearPath()
  393. }
  394. // FillPreserve fills the current path with the current color. Open subpaths
  395. // are implicity closed. The path is preserved after this operation.
  396. func (dc *Context) FillPreserve() {
  397. var painter raster.Painter
  398. if dc.mask == nil {
  399. if pattern, ok := dc.fillPattern.(*solidPattern); ok {
  400. // with a nil mask and a solid color pattern, we can be more efficient
  401. // TODO: refactor so we don't have to do this type assertion stuff?
  402. p := raster.NewRGBAPainter(dc.im)
  403. p.SetColor(pattern.color)
  404. painter = p
  405. }
  406. }
  407. if painter == nil {
  408. painter = newPatternPainter(dc.im, dc.mask, dc.fillPattern)
  409. }
  410. dc.fill(painter)
  411. }
  412. // Fill fills the current path with the current color. Open subpaths
  413. // are implicity closed. The path is cleared after this operation.
  414. func (dc *Context) Fill() {
  415. dc.FillPreserve()
  416. dc.ClearPath()
  417. }
  418. // ClipPreserve updates the clipping region by intersecting the current
  419. // clipping region with the current path as it would be filled by dc.Fill().
  420. // The path is preserved after this operation.
  421. func (dc *Context) ClipPreserve() {
  422. clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
  423. painter := raster.NewAlphaOverPainter(clip)
  424. dc.fill(painter)
  425. if dc.mask == nil {
  426. dc.mask = clip
  427. } else {
  428. mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
  429. draw.DrawMask(mask, mask.Bounds(), clip, image.ZP, dc.mask, image.ZP, draw.Over)
  430. dc.mask = mask
  431. }
  432. }
  433. // SetMask allows you to directly set the *image.Alpha to be used as a clipping
  434. // mask. It must be the same size as the context, else an error is returned
  435. // and the mask is unchanged.
  436. func (dc *Context) SetMask(mask *image.Alpha) error {
  437. if mask.Bounds().Size() != dc.im.Bounds().Size() {
  438. return errors.New("mask size must match context size")
  439. }
  440. dc.mask = mask
  441. return nil
  442. }
  443. // AsMask returns an *image.Alpha representing the alpha channel of this
  444. // context. This can be useful for advanced clipping operations where you first
  445. // render the mask geometry and then use it as a mask.
  446. func (dc *Context) AsMask() *image.Alpha {
  447. mask := image.NewAlpha(dc.im.Bounds())
  448. draw.Draw(mask, dc.im.Bounds(), dc.im, image.ZP, draw.Src)
  449. return mask
  450. }
  451. // InvertMask inverts the alpha values in the current clipping mask such that
  452. // a fully transparent region becomes fully opaque and vice versa.
  453. func (dc *Context) InvertMask() {
  454. if dc.mask == nil {
  455. dc.mask = image.NewAlpha(dc.im.Bounds())
  456. } else {
  457. for i, a := range dc.mask.Pix {
  458. dc.mask.Pix[i] = 255 - a
  459. }
  460. }
  461. }
  462. // Clip updates the clipping region by intersecting the current
  463. // clipping region with the current path as it would be filled by dc.Fill().
  464. // The path is cleared after this operation.
  465. func (dc *Context) Clip() {
  466. dc.ClipPreserve()
  467. dc.ClearPath()
  468. }
  469. // ResetClip clears the clipping region.
  470. func (dc *Context) ResetClip() {
  471. dc.mask = nil
  472. }
  473. // Convenient Drawing Functions
  474. // Clear fills the entire image with the current color.
  475. func (dc *Context) Clear() {
  476. src := image.NewUniform(dc.color)
  477. draw.Draw(dc.im, dc.im.Bounds(), src, image.ZP, draw.Src)
  478. }
  479. // SetPixel sets the color of the specified pixel using the current color.
  480. func (dc *Context) SetPixel(x, y int) {
  481. dc.im.Set(x, y, dc.color)
  482. }
  483. // DrawPoint is like DrawCircle but ensures that a circle of the specified
  484. // size is drawn regardless of the current transformation matrix. The position
  485. // is still transformed, but not the shape of the point.
  486. func (dc *Context) DrawPoint(x, y, r float64) {
  487. dc.Push()
  488. tx, ty := dc.TransformPoint(x, y)
  489. dc.Identity()
  490. dc.DrawCircle(tx, ty, r)
  491. dc.Pop()
  492. }
  493. func (dc *Context) DrawLine(x1, y1, x2, y2 float64) {
  494. dc.MoveTo(x1, y1)
  495. dc.LineTo(x2, y2)
  496. }
  497. func (dc *Context) DrawRectangle(x, y, w, h float64) {
  498. dc.NewSubPath()
  499. dc.MoveTo(x, y)
  500. dc.LineTo(x+w, y)
  501. dc.LineTo(x+w, y+h)
  502. dc.LineTo(x, y+h)
  503. dc.ClosePath()
  504. }
  505. func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) {
  506. x0, x1, x2, x3 := x, x+r, x+w-r, x+w
  507. y0, y1, y2, y3 := y, y+r, y+h-r, y+h
  508. dc.NewSubPath()
  509. dc.MoveTo(x1, y0)
  510. dc.LineTo(x2, y0)
  511. dc.DrawArc(x2, y1, r, Radians(270), Radians(360))
  512. dc.LineTo(x3, y2)
  513. dc.DrawArc(x2, y2, r, Radians(0), Radians(90))
  514. dc.LineTo(x1, y3)
  515. dc.DrawArc(x1, y2, r, Radians(90), Radians(180))
  516. dc.LineTo(x0, y1)
  517. dc.DrawArc(x1, y1, r, Radians(180), Radians(270))
  518. dc.ClosePath()
  519. }
  520. func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
  521. const n = 16
  522. for i := 0; i < n; i++ {
  523. p1 := float64(i+0) / n
  524. p2 := float64(i+1) / n
  525. a1 := angle1 + (angle2-angle1)*p1
  526. a2 := angle1 + (angle2-angle1)*p2
  527. x0 := x + rx*math.Cos(a1)
  528. y0 := y + ry*math.Sin(a1)
  529. x1 := x + rx*math.Cos((a1+a2)/2)
  530. y1 := y + ry*math.Sin((a1+a2)/2)
  531. x2 := x + rx*math.Cos(a2)
  532. y2 := y + ry*math.Sin(a2)
  533. cx := 2*x1 - x0/2 - x2/2
  534. cy := 2*y1 - y0/2 - y2/2
  535. if i == 0 {
  536. if dc.hasCurrent {
  537. dc.LineTo(x0, y0)
  538. } else {
  539. dc.MoveTo(x0, y0)
  540. }
  541. }
  542. dc.QuadraticTo(cx, cy, x2, y2)
  543. }
  544. }
  545. func (dc *Context) DrawEllipse(x, y, rx, ry float64) {
  546. dc.NewSubPath()
  547. dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi)
  548. dc.ClosePath()
  549. }
  550. func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) {
  551. dc.DrawEllipticalArc(x, y, r, r, angle1, angle2)
  552. }
  553. func (dc *Context) DrawCircle(x, y, r float64) {
  554. dc.NewSubPath()
  555. dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi)
  556. dc.ClosePath()
  557. }
  558. func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) {
  559. angle := 2 * math.Pi / float64(n)
  560. rotation -= math.Pi / 2
  561. if n%2 == 0 {
  562. rotation += angle / 2
  563. }
  564. dc.NewSubPath()
  565. for i := 0; i < n; i++ {
  566. a := rotation + angle*float64(i)
  567. dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a))
  568. }
  569. dc.ClosePath()
  570. }
  571. // DrawImage draws the specified image at the specified point.
  572. func (dc *Context) DrawImage(im image.Image, x, y int) {
  573. dc.DrawImageAnchored(im, x, y, 0, 0)
  574. }
  575. // DrawImageAnchored draws the specified image at the specified anchor point.
  576. // The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
  577. // image. Use ax=0.5, ay=0.5 to center the image at the specified point.
  578. func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) {
  579. s := im.Bounds().Size()
  580. x -= int(ax * float64(s.X))
  581. y -= int(ay * float64(s.Y))
  582. transformer := draw.BiLinear
  583. fx, fy := float64(x), float64(y)
  584. m := dc.matrix.Translate(fx, fy)
  585. s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
  586. if dc.mask == nil {
  587. transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, nil)
  588. } else {
  589. transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, &draw.Options{
  590. DstMask: dc.mask,
  591. DstMaskP: image.ZP,
  592. })
  593. }
  594. }
  595. // Text Functions
  596. func (dc *Context) SetFontFace(fontFace font.Face) {
  597. dc.fontFace = fontFace
  598. dc.fontHeight = float64(fontFace.Metrics().Height) / 64
  599. }
  600. func (dc *Context) LoadFontFace(path string, points float64) error {
  601. face, err := LoadFontFace(path, points)
  602. if err == nil {
  603. dc.fontFace = face
  604. dc.fontHeight = points * 72 / 96
  605. }
  606. return err
  607. }
  608. func (dc *Context) FontHeight() float64 {
  609. return dc.fontHeight
  610. }
  611. func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) {
  612. d := &font.Drawer{
  613. Dst: im,
  614. Src: image.NewUniform(dc.color),
  615. Face: dc.fontFace,
  616. Dot: fixp(x, y),
  617. }
  618. // based on Drawer.DrawString() in golang.org/x/image/font/font.go
  619. prevC := rune(-1)
  620. for _, c := range s {
  621. if prevC >= 0 {
  622. d.Dot.X += d.Face.Kern(prevC, c)
  623. }
  624. dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
  625. if !ok {
  626. // TODO: is falling back on the U+FFFD glyph the responsibility of
  627. // the Drawer or the Face?
  628. // TODO: set prevC = '\ufffd'?
  629. continue
  630. }
  631. sr := dr.Sub(dr.Min)
  632. transformer := draw.BiLinear
  633. fx, fy := float64(dr.Min.X), float64(dr.Min.Y)
  634. m := dc.matrix.Translate(fx, fy)
  635. s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
  636. transformer.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{
  637. SrcMask: mask,
  638. SrcMaskP: maskp,
  639. })
  640. d.Dot.X += advance
  641. prevC = c
  642. }
  643. }
  644. // DrawString draws the specified text at the specified point.
  645. func (dc *Context) DrawString(s string, x, y float64) {
  646. dc.DrawStringAnchored(s, x, y, 0, 0)
  647. }
  648. // DrawStringAnchored draws the specified text at the specified anchor point.
  649. // The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
  650. // text. Use ax=0.5, ay=0.5 to center the text at the specified point.
  651. func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) {
  652. w, h := dc.MeasureString(s)
  653. x -= ax * w
  654. y += ay * h
  655. if dc.mask == nil {
  656. dc.drawString(dc.im, s, x, y)
  657. } else {
  658. im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
  659. dc.drawString(im, s, x, y)
  660. draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
  661. }
  662. }
  663. // DrawStringWrapped word-wraps the specified string to the given max width
  664. // and then draws it at the specified anchor point using the given line
  665. // spacing and text alignment.
  666. func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) {
  667. lines := dc.WordWrap(s, width)
  668. // sync h formula with MeasureMultilineString
  669. h := float64(len(lines)) * dc.fontHeight * lineSpacing
  670. h -= (lineSpacing - 1) * dc.fontHeight
  671. x -= ax * width
  672. y -= ay * h
  673. switch align {
  674. case AlignLeft:
  675. ax = 0
  676. case AlignCenter:
  677. ax = 0.5
  678. x += width / 2
  679. case AlignRight:
  680. ax = 1
  681. x += width
  682. }
  683. ay = 1
  684. for _, line := range lines {
  685. dc.DrawStringAnchored(line, x, y, ax, ay)
  686. y += dc.fontHeight * lineSpacing
  687. }
  688. }
  689. func (dc *Context) MeasureMultilineString(s string, lineSpacing float64) (width, height float64) {
  690. lines := strings.Split(s, "\n")
  691. // sync h formula with DrawStringWrapped
  692. height = float64(len(lines)) * dc.fontHeight * lineSpacing
  693. height -= (lineSpacing - 1) * dc.fontHeight
  694. d := &font.Drawer{
  695. Face: dc.fontFace,
  696. }
  697. // max width from lines
  698. for _, line := range lines {
  699. adv := d.MeasureString(line)
  700. currentWidth := float64(adv >> 6) // from gg.Context.MeasureString
  701. if currentWidth > width {
  702. width = currentWidth
  703. }
  704. }
  705. return width, height
  706. }
  707. // MeasureString returns the rendered width and height of the specified text
  708. // given the current font face.
  709. func (dc *Context) MeasureString(s string) (w, h float64) {
  710. d := &font.Drawer{
  711. Face: dc.fontFace,
  712. }
  713. a := d.MeasureString(s)
  714. return float64(a >> 6), dc.fontHeight
  715. }
  716. // WordWrap wraps the specified string to the given max width and current
  717. // font face.
  718. func (dc *Context) WordWrap(s string, w float64) []string {
  719. return wordWrap(dc, s, w)
  720. }
  721. // Transformation Matrix Operations
  722. // Identity resets the current transformation matrix to the identity matrix.
  723. // This results in no translating, scaling, rotating, or shearing.
  724. func (dc *Context) Identity() {
  725. dc.matrix = Identity()
  726. }
  727. // Translate updates the current matrix with a translation.
  728. func (dc *Context) Translate(x, y float64) {
  729. dc.matrix = dc.matrix.Translate(x, y)
  730. }
  731. // Scale updates the current matrix with a scaling factor.
  732. // Scaling occurs about the origin.
  733. func (dc *Context) Scale(x, y float64) {
  734. dc.matrix = dc.matrix.Scale(x, y)
  735. }
  736. // ScaleAbout updates the current matrix with a scaling factor.
  737. // Scaling occurs about the specified point.
  738. func (dc *Context) ScaleAbout(sx, sy, x, y float64) {
  739. dc.Translate(x, y)
  740. dc.Scale(sx, sy)
  741. dc.Translate(-x, -y)
  742. }
  743. // Rotate updates the current matrix with a clockwise rotation.
  744. // Rotation occurs about the origin. Angle is specified in radians.
  745. func (dc *Context) Rotate(angle float64) {
  746. dc.matrix = dc.matrix.Rotate(angle)
  747. }
  748. // RotateAbout updates the current matrix with a clockwise rotation.
  749. // Rotation occurs about the specified point. Angle is specified in radians.
  750. func (dc *Context) RotateAbout(angle, x, y float64) {
  751. dc.Translate(x, y)
  752. dc.Rotate(angle)
  753. dc.Translate(-x, -y)
  754. }
  755. // Shear updates the current matrix with a shearing angle.
  756. // Shearing occurs about the origin.
  757. func (dc *Context) Shear(x, y float64) {
  758. dc.matrix = dc.matrix.Shear(x, y)
  759. }
  760. // ShearAbout updates the current matrix with a shearing angle.
  761. // Shearing occurs about the specified point.
  762. func (dc *Context) ShearAbout(sx, sy, x, y float64) {
  763. dc.Translate(x, y)
  764. dc.Shear(sx, sy)
  765. dc.Translate(-x, -y)
  766. }
  767. // TransformPoint multiplies the specified point by the current matrix,
  768. // returning a transformed position.
  769. func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) {
  770. return dc.matrix.TransformPoint(x, y)
  771. }
  772. // InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at
  773. // the bottom of the image.
  774. func (dc *Context) InvertY() {
  775. dc.Translate(0, float64(dc.height))
  776. dc.Scale(1, -1)
  777. }
  778. // Stack
  779. // Push saves the current state of the context for later retrieval. These
  780. // can be nested.
  781. func (dc *Context) Push() {
  782. x := *dc
  783. dc.stack = append(dc.stack, &x)
  784. }
  785. // Pop restores the last saved context state from the stack.
  786. func (dc *Context) Pop() {
  787. before := *dc
  788. s := dc.stack
  789. x, s := s[len(s)-1], s[:len(s)-1]
  790. *dc = *x
  791. dc.mask = before.mask
  792. dc.strokePath = before.strokePath
  793. dc.fillPath = before.fillPath
  794. dc.start = before.start
  795. dc.current = before.current
  796. dc.hasCurrent = before.hasCurrent
  797. }