-
Notifications
You must be signed in to change notification settings - Fork 0
/
paint.go
335 lines (279 loc) · 11.1 KB
/
paint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Copyright (c) 2018, The GoKi Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gist
import (
"image"
"image/color"
"github.com/goki/gi/units"
"github.com/goki/ki/ki"
"github.com/goki/ki/kit"
"github.com/goki/mat32"
)
// Painter defines an interface for anything that has a Paint style on it
type Painter interface {
Paint() *Paint
}
// Paint provides the styling parameters for rendering
type Paint struct {
Off bool `desc:"prop: display:none -- node and everything below it are off, non-rendering"`
Display bool `xml:"display" desc:"todo big enum of how to display item -- controls layout etc"`
StrokeStyle Stroke `desc:"stroke (line drawing) parameters"`
FillStyle Fill `desc:"fill (region filling) parameters"`
FontStyle Font `desc:"font also has global opacity setting, along with generic color, background-color settings, which can be copied into stroke / fill as needed"`
TextStyle Text `desc:"font also has global opacity setting, along with generic color, background-color settings, which can be copied into stroke / fill as needed"`
VecEff VectorEffects `xml:"vector-effect" desc:"prop: vector-effect = various rendering special effects settings"`
XForm mat32.Mat2 `xml:"transform" desc:"prop: transform = our additions to transform -- pushed to render state"`
UnContext units.Context `xml:"-" desc:"units context -- parameters necessary for anchoring relative units"`
StyleSet bool `desc:"have the styles already been set?"`
PropsNil bool `desc:"set to true if parent node has no props -- allows optimization of styling"`
dotsSet bool
lastUnCtxt units.Context
}
func (pc *Paint) Defaults() {
pc.Off = false
pc.Display = true
pc.StyleSet = false
pc.StrokeStyle.Defaults()
pc.FillStyle.Defaults()
pc.FontStyle.Defaults()
pc.TextStyle.Defaults()
pc.XForm = mat32.Identity2D()
}
// CopyStyleFrom copies styles from another paint
func (pc *Paint) CopyStyleFrom(cp *Paint) {
pc.Off = cp.Off
pc.Display = cp.Display
pc.UnContext = cp.UnContext
pc.StrokeStyle = cp.StrokeStyle
pc.FillStyle = cp.FillStyle
pc.FontStyle = cp.FontStyle
pc.TextStyle = cp.TextStyle
pc.VecEff = cp.VecEff
}
// InheritFields from parent: Manual inheriting of values is much faster than
// automatic version!
func (pc *Paint) InheritFields(par *Paint) {
pc.FontStyle.InheritFields(&par.FontStyle)
pc.TextStyle.InheritFields(&par.TextStyle)
}
// SetStyleProps sets paint values based on given property map (name: value
// pairs), inheriting elements as appropriate from parent, and also having a
// default style for the "initial" setting
func (pc *Paint) SetStyleProps(par *Paint, props ki.Props, ctxt Context) {
if !pc.StyleSet && par != nil { // first time
pc.InheritFields(par)
}
pc.StyleFromProps(par, props, ctxt)
pc.StrokeStyle.SetStylePost(props)
pc.FillStyle.SetStylePost(props)
pc.FontStyle.SetStylePost(props)
pc.TextStyle.SetStylePost(props)
pc.PropsNil = (len(props) == 0)
pc.StyleSet = true
}
// StyleToDots runs ToDots on unit values, to compile down to raw pixels
func (pc *Paint) StyleToDots(uc *units.Context) {
}
// ToDotsImpl runs ToDots on unit values, to compile down to raw pixels
func (pc *Paint) ToDotsImpl(uc *units.Context) {
pc.StyleToDots(uc)
pc.StrokeStyle.ToDots(uc)
pc.FillStyle.ToDots(uc)
pc.FontStyle.ToDots(uc)
pc.TextStyle.ToDots(uc)
}
// SetUnitContextExt sets the unit context for external usage of paint
// outside of a Viewport, based on overall size of painting canvas.
// caches everything out in terms of raw pixel dots for rendering
// call at start of render.
func (pc *Paint) SetUnitContextExt(size image.Point) {
pc.UnContext.Defaults()
pc.UnContext.DPI = 96 // paint (SVG) context is always 96 = 1to1
pc.UnContext.SetSizes(float32(size.X), float32(size.Y), float32(size.X), float32(size.Y))
pc.FontStyle.SetUnitContext(&pc.UnContext)
pc.ToDotsImpl(&pc.UnContext)
pc.dotsSet = true
}
// ToDots runs ToDots on unit values, to compile down to raw pixels
func (pc *Paint) ToDots() {
if !(pc.dotsSet && pc.UnContext == pc.lastUnCtxt && pc.PropsNil) {
pc.ToDotsImpl(&pc.UnContext)
pc.dotsSet = true
pc.lastUnCtxt = pc.UnContext
}
}
//////////////////////////////////////////////////////////////////////////////////
// State query
// does the current Paint have an active stroke to render?
func (pc *Paint) HasStroke() bool {
return pc.StrokeStyle.On
}
// does the current Paint have an active fill to render?
func (pc *Paint) HasFill() bool {
return pc.FillStyle.On
}
// does the current Paint not have either a stroke or fill? in which case, often we just skip it
func (pc *Paint) HasNoStrokeOrFill() bool {
return (!pc.StrokeStyle.On && !pc.FillStyle.On)
}
/////////////////////////////////////////////////////////////////
// enums
type FillRules int
const (
FillRuleNonZero FillRules = iota
FillRuleEvenOdd
FillRulesN
)
//go:generate stringer -type=FillRules
var KiT_FillRules = kit.Enums.AddEnumAltLower(FillRulesN, kit.NotBitFlag, StylePropProps, "FillRules")
func (ev FillRules) MarshalJSON() ([]byte, error) { return kit.EnumMarshalJSON(ev) }
func (ev *FillRules) UnmarshalJSON(b []byte) error { return kit.EnumUnmarshalJSON(ev, b) }
// VectorEffects contains special effects for rendering
type VectorEffects int32
const (
VecEffNone VectorEffects = iota
// VecEffNonScalingStroke means that the stroke width is not affected by
// transform properties
VecEffNonScalingStroke
VecEffN
)
//go:generate stringer -type=VectorEffects
var KiT_VectorEffects = kit.Enums.AddEnumAltLower(VecEffN, kit.NotBitFlag, StylePropProps, "VecEff")
func (ev VectorEffects) MarshalJSON() ([]byte, error) { return kit.EnumMarshalJSON(ev) }
func (ev *VectorEffects) UnmarshalJSON(b []byte) error { return kit.EnumUnmarshalJSON(ev, b) }
// IMPORTANT: any changes here must be updated below in StyleFillFuncs
// Fill contains all the properties for filling a region
type Fill struct {
On bool `desc:"is fill active -- if property is none then false"`
Color ColorSpec `xml:"fill" desc:"prop: fill = fill color specification"`
Opacity float32 `xml:"fill-opacity" desc:"prop: fill-opacity = global alpha opacity / transparency factor"`
Rule FillRules `xml:"fill-rule" desc:"prop: fill-rule = rule for how to fill more complex shapes with crossing lines"`
}
// Defaults initializes default values for paint fill
func (pf *Fill) Defaults() {
pf.On = true // svg says fill is ON by default
pf.SetColor(color.Black)
pf.Rule = FillRuleNonZero
pf.Opacity = 1.0
}
// SetStylePost does some updating after setting the style from user properties
func (pf *Fill) SetStylePost(props ki.Props) {
if pf.Color.IsNil() {
pf.On = false
} else {
pf.On = true
}
}
// SetColor sets a solid fill color -- nil turns off filling
func (pf *Fill) SetColor(cl color.Color) {
if cl == nil {
pf.On = false
} else {
pf.On = true
pf.Color.Color.SetColor(cl)
pf.Color.Source = SolidColor
}
}
// SetColorSpec sets full color spec from source
func (pf *Fill) SetColorSpec(cl *ColorSpec) {
if cl == nil {
pf.On = false
} else {
pf.On = true
pf.Color.CopyFrom(cl)
}
}
// ToDots runs ToDots on unit values, to compile down to raw pixels
func (fs *Fill) ToDots(uc *units.Context) {
}
////////////////////////////////////////////////////////////////////////////////////
// Stroke
// end-cap of a line: stroke-linecap property in SVG
type LineCaps int
const (
LineCapButt LineCaps = iota
LineCapRound
LineCapSquare
// rasterx extension
LineCapCubic
// rasterx extension
LineCapQuadratic
LineCapsN
)
//go:generate stringer -type=LineCaps
var KiT_LineCaps = kit.Enums.AddEnumAltLower(LineCapsN, kit.NotBitFlag, StylePropProps, "LineCaps")
func (ev LineCaps) MarshalJSON() ([]byte, error) { return kit.EnumMarshalJSON(ev) }
func (ev *LineCaps) UnmarshalJSON(b []byte) error { return kit.EnumUnmarshalJSON(ev, b) }
// the way in which lines are joined together: stroke-linejoin property in SVG
type LineJoins int
const (
LineJoinMiter LineJoins = iota
LineJoinMiterClip
LineJoinRound
LineJoinBevel
LineJoinArcs
// rasterx extension
LineJoinArcsClip
LineJoinsN
)
//go:generate stringer -type=LineJoins
var KiT_LineJoins = kit.Enums.AddEnumAltLower(LineJoinsN, kit.NotBitFlag, StylePropProps, "LineJoins")
func (ev LineJoins) MarshalJSON() ([]byte, error) { return kit.EnumMarshalJSON(ev) }
func (ev *LineJoins) UnmarshalJSON(b []byte) error { return kit.EnumUnmarshalJSON(ev, b) }
// IMPORTANT: any changes here must be updated below in StyleStrokeFuncs
// Stroke contains all the properties for painting a line
type Stroke struct {
On bool `desc:"is stroke active -- if property is none then false"`
Color ColorSpec `xml:"stroke" desc:"prop: stroke = stroke color specification"`
Opacity float32 `xml:"stroke-opacity" desc:"prop: stroke-opacity = global alpha opacity / transparency factor"`
Width units.Value `xml:"stroke-width" desc:"prop: stroke-width = line width"`
MinWidth units.Value `xml:"stroke-min-width" desc:"prop: stroke-min-width = minimum line width used for rendering -- if width is > 0, then this is the smallest line width -- this value is NOT subject to transforms so is in absolute dot values, and is ignored if vector-effects non-scaling-stroke is used -- this is an extension of the SVG / CSS standard"`
Dashes []float64 `xml:"stroke-dasharray" desc:"prop: stroke-dasharray = dash pattern, in terms of alternating on and off distances -- e.g., [4 4] = 4 pixels on, 4 pixels off. Currently only supporting raw pixel numbers, but in principle should support units."`
Cap LineCaps `xml:"stroke-linecap" desc:"prop: stroke-linecap = how to draw the end cap of lines"`
Join LineJoins `xml:"stroke-linejoin" desc:"prop: stroke-linejoin = how to join line segments"`
MiterLimit float32 `xml:"stroke-miterlimit" min:"1" desc:"prop: stroke-miterlimit = limit of how far to miter -- must be 1 or larger"`
}
// Defaults initializes default values for paint stroke
func (ps *Stroke) Defaults() {
ps.On = false // svg says default is off
ps.SetColor(Black)
ps.Width.Set(1.0, units.Px)
ps.MinWidth.Set(.5, units.Dot)
ps.Cap = LineCapButt
ps.Join = LineJoinMiter // Miter not yet supported, but that is the default -- falls back on bevel
ps.MiterLimit = 10.0
ps.Opacity = 1.0
}
// SetStylePost does some updating after setting the style from user properties
func (ps *Stroke) SetStylePost(props ki.Props) {
if ps.Color.IsNil() {
ps.On = false
} else {
ps.On = true
}
}
// SetColor sets a solid stroke color -- nil turns off stroking
func (ps *Stroke) SetColor(cl color.Color) {
if cl == nil {
ps.On = false
} else {
ps.On = true
ps.Color.Color.SetColor(cl)
ps.Color.Source = SolidColor
}
}
// SetColorSpec sets full color spec from source
func (ps *Stroke) SetColorSpec(cl *ColorSpec) {
if cl == nil {
ps.On = false
} else {
ps.On = true
ps.Color.CopyFrom(cl)
}
}
// ToDots runs ToDots on unit values, to compile down to raw pixels
func (ss *Stroke) ToDots(uc *units.Context) {
ss.Width.ToDots(uc)
ss.MinWidth.ToDots(uc)
}