...

Source file src/gonum.org/v1/plot/vg/vgimg/vgimg.go

Documentation: gonum.org/v1/plot/vg/vgimg

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package vgimg implements the vg.Canvas interface using
     6  // git.sr.ht/~sbinet/gg as a backend to output raster images.
     7  package vgimg // import "gonum.org/v1/plot/vg/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  // Canvas implements the vg.Canvas interface,
    50  // drawing to an image.Image using draw2d.
    51  type Canvas struct {
    52  	ctx   *gg.Context
    53  	img   draw.Image
    54  	w, h  vg.Length
    55  	color []color.Color
    56  
    57  	// dpi is the number of dots per inch for this canvas.
    58  	dpi int
    59  
    60  	// width is the current line width.
    61  	width vg.Length
    62  
    63  	// backgroundColor is the background color, set by
    64  	// UseBackgroundColor.
    65  	backgroundColor color.Color
    66  }
    67  
    68  const (
    69  	// DefaultDPI is the default dot resolution for image
    70  	// drawing in dots per inch.
    71  	DefaultDPI = 96
    72  
    73  	// DefaultWidth and DefaultHeight are the default canvas
    74  	// dimensions.
    75  	DefaultWidth  = 4 * vg.Inch
    76  	DefaultHeight = 4 * vg.Inch
    77  )
    78  
    79  // New returns a new image canvas.
    80  func New(w, h vg.Length) *Canvas {
    81  	return NewWith(UseWH(w, h), UseBackgroundColor(color.White))
    82  }
    83  
    84  // NewWith returns a new image canvas created according to the specified
    85  // options. The currently accepted options are UseWH,
    86  // UseDPI, UseImage, and UseImageWithContext.
    87  // Each of the options specifies the size of the canvas (UseWH, UseImage),
    88  // the resolution of the canvas (UseDPI), or both (useImageWithContext).
    89  // If size or resolution are not specified, defaults are used.
    90  // It panics if size and resolution are overspecified (i.e., too many options are
    91  // passed).
    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 { // h should also == 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  // These constants are used to ensure that the options
   135  // used when initializing a canvas are compatible with
   136  // each other.
   137  const (
   138  	setsDPI uint32 = 1 << iota
   139  	setsSize
   140  	setsBackground
   141  )
   142  
   143  type option func(*Canvas) uint32
   144  
   145  // UseWH specifies the width and height of the canvas.
   146  // The size is rounded up to the nearest pixel.
   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  // UseDPI sets the dots per inch of a canvas. It should only be
   158  // used as an option argument when initializing a new canvas.
   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  // UseImage specifies an image to create
   170  // the canvas from. The
   171  // minimum point of the given image
   172  // should probably be 0,0.
   173  //
   174  // Note that a copy of the input image is performed.
   175  // This means that modifications applied to the canvas are not reflected
   176  // on the original image.
   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  // UseImageWithContext specifies both an image
   185  // and a graphic context to create the canvas from.
   186  // The minimum point of the given image
   187  // should probably be 0,0.
   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  // UseBackgroundColor specifies the image background color.
   197  // Without UseBackgroundColor, the default color is white.
   198  func UseBackgroundColor(c color.Color) option {
   199  	return func(canvas *Canvas) uint32 {
   200  		canvas.backgroundColor = c
   201  		return setsBackground
   202  	}
   203  }
   204  
   205  // Image returns the image the canvas is drawing to.
   206  //
   207  // The dimensions of the returned image must not be modified.
   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  // DPI returns the resolution of the receiver in pixels per inch.
   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  // DrawImage implements the vg.Canvas.DrawImage method.
   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  // WriterCounter implements the io.Writer interface, and counts
   362  // the total number of bytes written.
   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  // A JpegCanvas is an image canvas with a WriteTo method
   375  // that writes a jpeg image.
   376  type JpegCanvas struct {
   377  	*Canvas
   378  }
   379  
   380  // WriteTo implements the io.WriterTo interface, writing a jpeg image.
   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  // A PngCanvas is an image canvas with a WriteTo method that
   392  // writes a png image.
   393  type PngCanvas struct {
   394  	*Canvas
   395  }
   396  
   397  // WriteTo implements the io.WriterTo interface, writing a png image.
   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  // A TiffCanvas is an image canvas with a WriteTo method that
   409  // writes a tiff image.
   410  type TiffCanvas struct {
   411  	*Canvas
   412  }
   413  
   414  // WriteTo implements the io.WriterTo interface, writing a tiff image.
   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