1
2
3
4
5
6
7 package vgimg
8
9 import (
10 "bufio"
11 "fmt"
12 "image"
13 "image/color"
14 "image/draw"
15 "image/jpeg"
16 "image/png"
17 "io"
18
19 "git.sr.ht/~sbinet/gg"
20 "golang.org/x/image/tiff"
21
22 "gonum.org/v1/plot/font"
23 "gonum.org/v1/plot/vg"
24 vgdraw "gonum.org/v1/plot/vg/draw"
25 )
26
27 func init() {
28 vgdraw.RegisterFormat("png", func(w, h vg.Length) vg.CanvasWriterTo {
29 return PngCanvas{Canvas: New(w, h)}
30 })
31
32 vgdraw.RegisterFormat("jpg", func(w, h vg.Length) vg.CanvasWriterTo {
33 return JpegCanvas{Canvas: New(w, h)}
34 })
35
36 vgdraw.RegisterFormat("jpeg", func(w, h vg.Length) vg.CanvasWriterTo {
37 return JpegCanvas{Canvas: New(w, h)}
38 })
39
40 vgdraw.RegisterFormat("tif", func(w, h vg.Length) vg.CanvasWriterTo {
41 return TiffCanvas{Canvas: New(w, h)}
42 })
43
44 vgdraw.RegisterFormat("tiff", func(w, h vg.Length) vg.CanvasWriterTo {
45 return TiffCanvas{Canvas: New(w, h)}
46 })
47 }
48
49
50
51 type Canvas struct {
52 ctx *gg.Context
53 img draw.Image
54 w, h vg.Length
55 color []color.Color
56
57
58 dpi int
59
60
61 width vg.Length
62
63
64
65 backgroundColor color.Color
66 }
67
68 const (
69
70
71 DefaultDPI = 96
72
73
74
75 DefaultWidth = 4 * vg.Inch
76 DefaultHeight = 4 * vg.Inch
77 )
78
79
80 func New(w, h vg.Length) *Canvas {
81 return NewWith(UseWH(w, h), UseBackgroundColor(color.White))
82 }
83
84
85
86
87
88
89
90
91
92 func NewWith(o ...option) *Canvas {
93 c := new(Canvas)
94 c.backgroundColor = color.White
95 var g uint32
96 for _, opt := range o {
97 f := opt(c)
98 if g&f != 0 {
99 panic("incompatible options")
100 }
101 g |= f
102 }
103 if c.dpi == 0 {
104 c.dpi = DefaultDPI
105 }
106 if c.w == 0 {
107 if c.img == nil {
108 c.w = DefaultWidth
109 c.h = DefaultHeight
110 } else {
111 w := float64(c.img.Bounds().Max.X - c.img.Bounds().Min.X)
112 h := float64(c.img.Bounds().Max.Y - c.img.Bounds().Min.Y)
113 c.w = vg.Length(w/float64(c.dpi)) * vg.Inch
114 c.h = vg.Length(h/float64(c.dpi)) * vg.Inch
115 }
116 }
117 if c.img == nil {
118 w := c.w / vg.Inch * vg.Length(c.dpi)
119 h := c.h / vg.Inch * vg.Length(c.dpi)
120 c.img = draw.Image(image.NewRGBA(image.Rect(0, 0, int(w+0.5), int(h+0.5))))
121 }
122 if c.ctx == nil {
123 c.ctx = gg.NewContextForImage(c.img)
124 c.ctx.SetLineCapButt()
125 c.img = c.ctx.Image().(draw.Image)
126 c.ctx.InvertY()
127 }
128 draw.Draw(c.img, c.img.Bounds(), &image.Uniform{c.backgroundColor}, image.Point{}, draw.Src)
129 c.color = []color.Color{color.Black}
130 vg.Initialize(c)
131 return c
132 }
133
134
135
136
137 const (
138 setsDPI uint32 = 1 << iota
139 setsSize
140 setsBackground
141 )
142
143 type option func(*Canvas) uint32
144
145
146
147 func UseWH(w, h vg.Length) option {
148 return func(c *Canvas) uint32 {
149 if w <= 0 || h <= 0 {
150 panic("w and h must both be > 0.")
151 }
152 c.w, c.h = w, h
153 return setsSize
154 }
155 }
156
157
158
159 func UseDPI(dpi int) option {
160 if dpi <= 0 {
161 panic("DPI must be > 0.")
162 }
163 return func(c *Canvas) uint32 {
164 c.dpi = dpi
165 return setsDPI
166 }
167 }
168
169
170
171
172
173
174
175
176
177 func UseImage(img draw.Image) option {
178 return func(c *Canvas) uint32 {
179 c.img = img
180 return setsSize | setsBackground
181 }
182 }
183
184
185
186
187
188 func UseImageWithContext(img draw.Image, ctx *gg.Context) option {
189 return func(c *Canvas) uint32 {
190 c.img = img
191 c.ctx = ctx
192 return setsSize | setsBackground
193 }
194 }
195
196
197
198 func UseBackgroundColor(c color.Color) option {
199 return func(canvas *Canvas) uint32 {
200 canvas.backgroundColor = c
201 return setsBackground
202 }
203 }
204
205
206
207
208 func (c *Canvas) Image() draw.Image {
209 return c.img
210 }
211
212 func (c *Canvas) Size() (w, h vg.Length) {
213 return c.w, c.h
214 }
215
216 func (c *Canvas) SetLineWidth(w vg.Length) {
217 c.width = w
218 c.ctx.SetLineWidth(w.Dots(c.DPI()))
219 }
220
221 func (c *Canvas) SetLineDash(ds []vg.Length, offs vg.Length) {
222 dashes := make([]float64, len(ds))
223 for i, d := range ds {
224 dashes[i] = d.Dots(c.DPI())
225 }
226 c.ctx.SetDashOffset(offs.Dots(c.DPI()))
227 c.ctx.SetDash(dashes...)
228 }
229
230 func (c *Canvas) SetColor(clr color.Color) {
231 if clr == nil {
232 clr = color.Black
233 }
234 c.ctx.SetColor(clr)
235 c.color[len(c.color)-1] = clr
236 }
237
238 func (c *Canvas) Rotate(t float64) {
239 c.ctx.Rotate(t)
240 }
241
242 func (c *Canvas) Translate(pt vg.Point) {
243 c.ctx.Translate(pt.X.Dots(c.DPI()), pt.Y.Dots(c.DPI()))
244 }
245
246 func (c *Canvas) Scale(x, y float64) {
247 c.ctx.Scale(x, y)
248 }
249
250 func (c *Canvas) Push() {
251 c.color = append(c.color, c.color[len(c.color)-1])
252 c.ctx.Push()
253 }
254
255 func (c *Canvas) Pop() {
256 c.color = c.color[:len(c.color)-1]
257 c.ctx.Pop()
258 }
259
260 func (c *Canvas) Stroke(p vg.Path) {
261 if c.width <= 0 {
262 return
263 }
264 c.outline(p)
265 c.ctx.Stroke()
266 }
267
268 func (c *Canvas) Fill(p vg.Path) {
269 c.outline(p)
270 c.ctx.Fill()
271 }
272
273 func (c *Canvas) outline(p vg.Path) {
274 for _, comp := range p {
275 switch comp.Type {
276 case vg.MoveComp:
277 c.ctx.MoveTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
278
279 case vg.LineComp:
280 c.ctx.LineTo(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()))
281
282 case vg.ArcComp:
283 c.ctx.DrawArc(comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
284 comp.Radius.Dots(c.DPI()),
285 comp.Start, comp.Start+comp.Angle,
286 )
287
288 case vg.CurveComp:
289 switch len(comp.Control) {
290 case 1:
291 c.ctx.QuadraticTo(
292 comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
293 comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
294 )
295 case 2:
296 c.ctx.CubicTo(
297 comp.Control[0].X.Dots(c.DPI()), comp.Control[0].Y.Dots(c.DPI()),
298 comp.Control[1].X.Dots(c.DPI()), comp.Control[1].Y.Dots(c.DPI()),
299 comp.Pos.X.Dots(c.DPI()), comp.Pos.Y.Dots(c.DPI()),
300 )
301 default:
302 panic("vgimg: invalid number of control points")
303 }
304
305 case vg.CloseComp:
306 c.ctx.ClosePath()
307
308 default:
309 panic(fmt.Sprintf("Unknown path component: %d", comp.Type))
310 }
311 }
312 }
313
314
315 func (c *Canvas) DPI() float64 {
316 return float64(c.dpi)
317 }
318
319 func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
320 if font.Font.Size == 0 {
321 return
322 }
323
324 c.ctx.Push()
325 defer c.ctx.Pop()
326
327 face := font.FontFace(c.DPI())
328 defer face.Close()
329
330 c.ctx.SetFontFace(face)
331
332 x := pt.X.Dots(c.DPI())
333 y := pt.Y.Dots(c.DPI())
334 h := c.h.Dots(c.DPI())
335
336 c.ctx.InvertY()
337 c.ctx.DrawString(str, x, h-y)
338 }
339
340
341 func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
342 var (
343 dpi = c.DPI()
344 min = rect.Min
345 xmin = min.X.Dots(dpi)
346 ymin = min.Y.Dots(dpi)
347 rsz = rect.Size()
348 width = rsz.X.Dots(dpi)
349 height = rsz.Y.Dots(dpi)
350 dx = float64(img.Bounds().Dx())
351 dy = float64(img.Bounds().Dy())
352 )
353 c.ctx.Push()
354 c.ctx.Scale(1, -1)
355 c.ctx.Translate(xmin, -ymin-height)
356 c.ctx.Scale(width/dx, height/dy)
357 c.ctx.DrawImage(img, 0, 0)
358 c.ctx.Pop()
359 }
360
361
362
363 type writerCounter struct {
364 io.Writer
365 n int64
366 }
367
368 func (w *writerCounter) Write(p []byte) (int, error) {
369 n, err := w.Writer.Write(p)
370 w.n += int64(n)
371 return n, err
372 }
373
374
375
376 type JpegCanvas struct {
377 *Canvas
378 }
379
380
381 func (c JpegCanvas) WriteTo(w io.Writer) (int64, error) {
382 wc := writerCounter{Writer: w}
383 b := bufio.NewWriter(&wc)
384 if err := jpeg.Encode(b, c.img, nil); err != nil {
385 return wc.n, err
386 }
387 err := b.Flush()
388 return wc.n, err
389 }
390
391
392
393 type PngCanvas struct {
394 *Canvas
395 }
396
397
398 func (c PngCanvas) WriteTo(w io.Writer) (int64, error) {
399 wc := writerCounter{Writer: w}
400 b := bufio.NewWriter(&wc)
401 if err := png.Encode(b, c.img); err != nil {
402 return wc.n, err
403 }
404 err := b.Flush()
405 return wc.n, err
406 }
407
408
409
410 type TiffCanvas struct {
411 *Canvas
412 }
413
414
415 func (c TiffCanvas) WriteTo(w io.Writer) (int64, error) {
416 wc := writerCounter{Writer: w}
417 b := bufio.NewWriter(&wc)
418 if err := tiff.Encode(b, c.img, nil); err != nil {
419 return wc.n, err
420 }
421 err := b.Flush()
422 return wc.n, err
423 }
424
View as plain text