Skip to content

Commit 8921d53

Browse files
committed
SVG: support fill-opacity and stroke-opacity, fixes #377
1 parent 6ffe497 commit 8921d53

2 files changed

Lines changed: 87 additions & 22 deletions

File tree

colors.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package canvas
33
import (
44
"image/color"
55
"math"
6+
"slices"
67
)
78

89
// RGB returns a color given by red, green, and blue ∈ [0,1].
@@ -25,6 +26,16 @@ func RGBA(r, g, b, a float64) color.RGBA {
2526
}
2627
}
2728

29+
func ToOpacity(col color.Color, a float64) color.RGBA {
30+
R, G, B, A := col.RGBA()
31+
return color.RGBA{
32+
R: uint8(float64(R) / float64(A) * a * 255.0),
33+
G: uint8(float64(G) / float64(A) * a * 255.0),
34+
B: uint8(float64(B) / float64(A) * a * 255.0),
35+
A: uint8(a * 255.0),
36+
}
37+
}
38+
2839
// Hex parses a CSS hexadecimal color such as e.g. #ff0000 or F00.
2940
func Hex(s string) color.RGBA {
3041
if 0 < len(s) && s[0] == '#' {
@@ -130,6 +141,16 @@ func (g Grad) ToRadial(c0 Point, r0 float64, c1 Point, r1 float64) *RadialGradie
130141
return grad
131142
}
132143

144+
func (g Grad) SetColorSpace(colorSpace ColorSpace) {
145+
if _, ok := colorSpace.(LinearColorSpace); ok {
146+
return
147+
}
148+
g = slices.Clone(g)
149+
for i := range g {
150+
g[i].Color = colorSpace.ToLinear(g[i].Color)
151+
}
152+
}
153+
133154
func colorLerp(c0, c1 color.RGBA, t float64) color.RGBA {
134155
r0, g0, b0, a0 := c0.RGBA()
135156
r1, g1, b1, a1 := c1.RGBA()
@@ -167,13 +188,7 @@ func NewLinearGradient(start, end Point) *LinearGradient {
167188

168189
// SetColorSpace sets the color space. Automatically called by the rasterizer.
169190
func (g *LinearGradient) SetColorSpace(colorSpace ColorSpace) Gradient {
170-
if _, ok := colorSpace.(LinearColorSpace); ok {
171-
return g
172-
}
173-
g.Grad = append(Grad{}, g.Grad...)
174-
for i := range g.Grad {
175-
g.Grad[i].Color = colorSpace.ToLinear(g.Grad[i].Color)
176-
}
191+
g.Grad.SetColorSpace(colorSpace)
177192
return g
178193
}
179194

@@ -219,13 +234,7 @@ func NewRadialGradient(c0 Point, r0 float64, c1 Point, r1 float64) *RadialGradie
219234

220235
// SetColorSpace sets the color space. Automatically called by the rasterizer.
221236
func (g *RadialGradient) SetColorSpace(colorSpace ColorSpace) Gradient {
222-
if _, ok := colorSpace.(LinearColorSpace); ok {
223-
return g
224-
}
225-
g.Grad = append(Grad{}, g.Grad...)
226-
for i := range g.Grad {
227-
g.Grad[i].Color = colorSpace.ToLinear(g.Grad[i].Color)
228-
}
237+
g.Grad.SetColorSpace(colorSpace)
229238
return g
230239
}
231240

svg.go

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -477,10 +477,7 @@ func (svg *svgParser) parseDefs(l *xml.Lexer) {
477477
stopColor := svg.parseColor(tag.attrs["stop-color"])
478478
if v, ok := tag.attrs["stop-opacity"]; ok {
479479
stopOpacity := svg.parseNumber(v)
480-
stopColor.R = uint8(float64(stopColor.R) / float64(stopColor.A) * stopOpacity * 255.0)
481-
stopColor.G = uint8(float64(stopColor.G) / float64(stopColor.A) * stopOpacity * 255.0)
482-
stopColor.B = uint8(float64(stopColor.B) / float64(stopColor.A) * stopOpacity * 255.0)
483-
stopColor.A = uint8(stopOpacity * 255.0)
480+
stopColor = ToOpacity(stopColor, stopOpacity)
484481
}
485482
grad.Add(offset, stopColor)
486483
}
@@ -552,10 +549,7 @@ func (svg *svgParser) parseDefs(l *xml.Lexer) {
552549
stopColor := svg.parseColor(tag.attrs["stop-color"])
553550
if v, ok := tag.attrs["stop-opacity"]; ok {
554551
stopOpacity := svg.parseNumber(v)
555-
stopColor.R = uint8(float64(stopColor.R) / float64(stopColor.A) * stopOpacity * 255.0)
556-
stopColor.G = uint8(float64(stopColor.G) / float64(stopColor.A) * stopOpacity * 255.0)
557-
stopColor.B = uint8(float64(stopColor.B) / float64(stopColor.A) * stopOpacity * 255.0)
558-
stopColor.A = uint8(stopOpacity * 255.0)
552+
stopColor = ToOpacity(stopColor, stopOpacity)
559553
}
560554
grad.Add(offset, stopColor)
561555
}
@@ -898,15 +892,77 @@ func (svg *svgParser) setAttribute(key, val string) {
898892
// Set a temporary fill so that DrawPath doesn't early return
899893
svg.ctx.SetFill(Paint{Color: Black})
900894
} else {
895+
alpha := float64(svg.ctx.Style.Fill.Color.A) / 255.0
901896
svg.ctx.SetFill(svg.parsePaint(val))
897+
if alpha != 1.0 {
898+
svg.ctx.Style.Fill.Color = ToOpacity(svg.ctx.Style.Fill.Color, alpha)
899+
if svg.ctx.Style.Fill.IsGradient() {
900+
switch grad := svg.ctx.Style.Fill.Gradient.(type) {
901+
case *LinearGradient:
902+
for i := range grad.Grad {
903+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
904+
}
905+
case *RadialGradient:
906+
for i := range grad.Grad {
907+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
908+
}
909+
}
910+
}
911+
}
912+
}
913+
case "fill-opacity":
914+
alpha := svg.parseNumber(val)
915+
svg.ctx.Style.Fill.Color = ToOpacity(svg.ctx.Style.Fill.Color, alpha)
916+
if svg.ctx.Style.Fill.IsGradient() {
917+
switch grad := svg.ctx.Style.Stroke.Gradient.(type) {
918+
case *LinearGradient:
919+
for i := range grad.Grad {
920+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
921+
}
922+
case *RadialGradient:
923+
for i := range grad.Grad {
924+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
925+
}
926+
}
902927
}
903928
case "stroke":
904929
if id := svg.parseUrlID(val); id != "" {
905930
svg.activeDefs["stroke"] = svg.defs[id]
906931
// Set a temporary stroke so that DrawPath doesn't early return
907932
svg.ctx.SetStroke(Paint{Color: Black})
908933
} else {
934+
alpha := float64(svg.ctx.Style.Stroke.Color.A) / 255.0
909935
svg.ctx.SetStroke(svg.parsePaint(val))
936+
if alpha != 1.0 {
937+
svg.ctx.Style.Stroke.Color = ToOpacity(svg.ctx.Style.Stroke.Color, alpha)
938+
if svg.ctx.Style.Stroke.IsGradient() {
939+
switch grad := svg.ctx.Style.Stroke.Gradient.(type) {
940+
case *LinearGradient:
941+
for i := range grad.Grad {
942+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
943+
}
944+
case *RadialGradient:
945+
for i := range grad.Grad {
946+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
947+
}
948+
}
949+
}
950+
}
951+
}
952+
case "stroke-opacity":
953+
alpha := svg.parseNumber(val)
954+
svg.ctx.Style.Stroke.Color = ToOpacity(svg.ctx.Style.Stroke.Color, alpha)
955+
if svg.ctx.Style.Stroke.IsGradient() {
956+
switch grad := svg.ctx.Style.Stroke.Gradient.(type) {
957+
case *LinearGradient:
958+
for i := range grad.Grad {
959+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
960+
}
961+
case *RadialGradient:
962+
for i := range grad.Grad {
963+
grad.Grad[i].Color = ToOpacity(grad.Grad[i].Color, alpha)
964+
}
965+
}
910966
}
911967
case "stroke-width":
912968
svg.ctx.SetStrokeWidth(svg.parseDimension(val, svg.diagonal))

0 commit comments

Comments
 (0)