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}