scan.go

  1// Package rasterx implements a rasterizer in go.
  2// By default rasterx uses ScannerGV to render images
  3// which uses the rasterizer in the golang.org/x/image/vector package.
  4// The freetype rasterizer under the GNU license can also be used, by
  5// downloading the scanFT package.
  6//
  7// Copyright 2018 All rights reserved.
  8// Created: 5/12/2018 by S.R.Wiley
  9package rasterx
 10
 11import (
 12	"image"
 13	"math"
 14
 15	"image/color"
 16	"image/draw"
 17
 18	"golang.org/x/image/math/fixed"
 19	"golang.org/x/image/vector"
 20)
 21
 22// At returns the color at the point x,y
 23func (c *ColorFuncImage) At(x, y int) color.Color {
 24	return c.colorFunc(x, y)
 25}
 26
 27type (
 28	// ColorFuncImage implements and image
 29	// using the provided colorFunc
 30	ColorFuncImage struct {
 31		image.Uniform
 32		colorFunc ColorFunc
 33	}
 34
 35	// ScannerGV uses the google vector rasterizer
 36	ScannerGV struct {
 37		r vector.Rasterizer
 38		//a, first fixed.Point26_6
 39		Dest                   draw.Image
 40		Targ                   image.Rectangle
 41		clipImage              *ClipImage
 42		Source                 image.Image
 43		Offset                 image.Point
 44		minX, minY, maxX, maxY fixed.Int26_6 // keep track of bounds
 45	}
 46)
 47
 48// ClipImage is a clipable ColorFuncImage
 49type ClipImage struct {
 50	ColorFuncImage
 51	clip image.Rectangle
 52}
 53
 54var noApha = color.RGBA{0, 0, 0, 0}
 55
 56// GetPathExtent returns the extent of the path
 57func (s *ScannerGV) GetPathExtent() fixed.Rectangle26_6 {
 58	return fixed.Rectangle26_6{Min: fixed.Point26_6{X: s.minX, Y: s.minY}, Max: fixed.Point26_6{X: s.maxX, Y: s.maxY}}
 59}
 60
 61// At returns the color of the ClipImage at the point x,y
 62func (c *ClipImage) At(x, y int) color.Color {
 63	p := image.Point{x, y}
 64	if p.In(c.clip) {
 65		return c.ColorFuncImage.At(x, y)
 66	}
 67	return noApha
 68}
 69
 70// SetWinding set the winding rule for the scanner
 71func (s *ScannerGV) SetWinding(useNonZeroWinding bool) {
 72	// no-op as scanner gv does not support even-odd winding
 73}
 74
 75// SetColor set the color type for the scanner
 76func (s *ScannerGV) SetColor(clr interface{}) {
 77	switch c := clr.(type) {
 78	case color.Color:
 79		s.clipImage.ColorFuncImage.Uniform.C = c
 80		if s.clipImage.clip == image.ZR {
 81			s.Source = &s.clipImage.ColorFuncImage.Uniform
 82		} else {
 83			s.clipImage.ColorFuncImage.colorFunc = func(x, y int) color.Color {
 84				return c
 85			}
 86			s.Source = s.clipImage
 87		}
 88	case ColorFunc:
 89		s.clipImage.ColorFuncImage.colorFunc = c
 90		if s.clipImage.clip == image.ZR {
 91			s.Source = &s.clipImage.ColorFuncImage
 92		} else {
 93			s.Source = s.clipImage
 94		}
 95	}
 96}
 97
 98// SetClip sets an optional clipping rectangle to restrict rendering only to
 99// that region -- if size is 0 then ignored (set to image.ZR to clear)
100func (s *ScannerGV) SetClip(rect image.Rectangle) {
101	s.clipImage.clip = rect
102	if s.Source == &s.clipImage.ColorFuncImage.Uniform {
103		s.SetColor(s.clipImage.ColorFuncImage.Uniform.C)
104	} else {
105		s.SetColor(s.clipImage.ColorFuncImage.colorFunc)
106	}
107}
108
109func (s *ScannerGV) set(a fixed.Point26_6) {
110	if s.maxX < a.X {
111		s.maxX = a.X
112	}
113	if s.maxY < a.Y {
114		s.maxY = a.Y
115	}
116	if s.minX > a.X {
117		s.minX = a.X
118	}
119	if s.minY > a.Y {
120		s.minY = a.Y
121	}
122}
123
124// Start starts a new path at the given point.
125func (s *ScannerGV) Start(a fixed.Point26_6) {
126	s.set(a)
127	s.r.MoveTo(float32(a.X)/64, float32(a.Y)/64)
128}
129
130// Line adds a linear segment to the current curve.
131func (s *ScannerGV) Line(b fixed.Point26_6) {
132	s.set(b)
133	s.r.LineTo(float32(b.X)/64, float32(b.Y)/64)
134}
135
136// Draw renders the accumulate scan to the desination
137func (s *ScannerGV) Draw() {
138	// This draws the entire bounds of the image, because
139	// at this point the alpha mask does not shift with the
140	// placement of the target rectangle in the vector rasterizer
141	s.r.Draw(s.Dest, s.Dest.Bounds(), s.Source, s.Offset)
142
143	// Remove the line above and uncomment the lines below if you
144	// are using a version of the vector rasterizer that shifts the alpha
145	// mask with the placement of the target
146
147	//	s.Targ.Min.X = int(s.minX >> 6)
148	//	s.Targ.Min.Y = int(s.minY >> 6)
149	//	s.Targ.Max.X = int(s.maxX>>6) + 1
150	//	s.Targ.Max.Y = int(s.maxY>>6) + 1
151	//	s.Targ = s.Targ.Intersect(s.Dest.Bounds())  // This check should be done by the rasterizer?
152	//	s.r.Draw(s.Dest, s.Targ, s.Source, s.Offset)
153}
154
155// Clear cancels any previous accumulated scans
156func (s *ScannerGV) Clear() {
157	p := s.r.Size()
158	s.r.Reset(p.X, p.Y)
159	const mxfi = fixed.Int26_6(math.MaxInt32)
160	s.minX, s.minY, s.maxX, s.maxY = mxfi, mxfi, -mxfi, -mxfi
161}
162
163// SetBounds sets the maximum width and height of the rasterized image and
164// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
165func (s *ScannerGV) SetBounds(width, height int) {
166	s.r.Reset(width, height)
167}
168
169// NewScannerGV creates a new Scanner with the given bounds.
170func NewScannerGV(width, height int, dest draw.Image,
171	targ image.Rectangle) *ScannerGV {
172	s := new(ScannerGV)
173	s.SetBounds(width, height)
174	s.Dest = dest
175	s.Targ = targ
176	s.clipImage = &ClipImage{}
177	s.clipImage.ColorFuncImage.Uniform.C = &color.RGBA{255, 0, 0, 255}
178	s.Source = &s.clipImage.ColorFuncImage.Uniform
179	s.Offset = image.Point{0, 0}
180	return s
181}