Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new canvas.RecolorSVG API #5345

Merged
merged 9 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion canvas/canvas.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package canvas

import "fyne.io/fyne/v2"
import (
"image/color"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/svg"
)

// Refresh instructs the containing canvas to refresh the specified obj.
func Refresh(obj fyne.CanvasObject) {
Expand All @@ -15,6 +20,19 @@ func Refresh(obj fyne.CanvasObject) {
}
}

// RecolorSVG takes a []byte containing SVG content, and returns
// new SVG content, re-colorized to be monochrome with the given color.
// The content can be assigned to a new fyne.StaticResource with an appropriate name
// to be used in a widget.Button, canvas.Image, etc.
//
// If an error occurs, the returned content will be the original un-modified content,
// and a non-nil error is returned.
//
// Since: 2.6
func RecolorSVG(svgContent []byte, color color.Color) ([]byte, error) {
return svg.Colorize(svgContent, color)
}

// repaint instructs the containing canvas to redraw, even if nothing changed.
func repaint(obj fyne.CanvasObject) {
app := fyne.CurrentApp()
Expand Down
6 changes: 5 additions & 1 deletion canvas/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ func (i *Image) updateReader() (io.ReadCloser, error) {
th := cache.WidgetTheme(i)
if th != nil {
col := th.Color(res.ThemeColorName(), fyne.CurrentApp().Settings().ThemeVariant())
content = svg.Colorize(content, col)
var err error
content, err = svg.Colorize(content, col)
if err != nil {
fyne.LogError("", err)
}
}
}
return io.NopCloser(bytes.NewReader(content)), nil
Expand Down
6 changes: 5 additions & 1 deletion internal/driver/glfw/menu_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,13 @@ func insertNativeMenuItem(nsMenu unsafe.Pointer, item *fyne.MenuItem, nextItemID
if _, isThemed := rsc.(*theme.ThemedResource); isThemed {
var r, g, b, a C.int
C.getTextColorRGBA(&r, &g, &b, &a)
content, err := svg.Colorize(rsc.Content(), color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)})
if err != nil {
fyne.LogError("", err)
}
rsc = &fyne.StaticResource{
StaticName: rsc.Name(),
StaticContent: svg.Colorize(rsc.Content(), color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)}),
StaticContent: content,
}
}
size := int(C.menuFontSize())
Expand Down
13 changes: 5 additions & 8 deletions internal/svg/svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,20 @@ import (
)

// Colorize creates a new SVG from a given one by replacing all fill colors by the given color.
func Colorize(src []byte, clr color.Color) []byte {
func Colorize(src []byte, clr color.Color) ([]byte, error) {
rdr := bytes.NewReader(src)
s, err := svgFromXML(rdr)
if err != nil {
fyne.LogError("could not load SVG, falling back to static content:", err)
return src
return src, fmt.Errorf("could not load SVG, falling back to static content: %v", err)
}
if err := s.replaceFillColor(clr); err != nil {
fyne.LogError("could not replace fill color, falling back to static content:", err)
return src
return src, fmt.Errorf("could not replace fill color, falling back to static content: %v", err)
}
colorized, err := xml.Marshal(s)
if err != nil {
fyne.LogError("could not marshal svg, falling back to static content:", err)
return src
return src, fmt.Errorf("could not marshal svg, falling back to static content: %v", err)
}
return colorized
return colorized, nil
}

type Decoder struct {
Expand Down
3 changes: 2 additions & 1 deletion internal/svg/svg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ func TestColorize(t *testing.T) {
t.Run(name, func(t *testing.T) {
bytes, err := os.ReadFile(filepath.Join("testdata", tt.svgFile))
require.NoError(t, err)
got := helperDrawSVG(t, Colorize(bytes, tt.color))
content, _ := Colorize(bytes, tt.color)
got := helperDrawSVG(t, content)
test.AssertImageMatches(t, tt.wantImage, got)
})
}
Expand Down
20 changes: 15 additions & 5 deletions theme/icons.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package theme

import (
"image/color"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/svg"
)
Expand Down Expand Up @@ -712,7 +714,7 @@ func (res *ThemedResource) ThemeColorName() fyne.ThemeColorName {

// Content returns the underlying content of the resource adapted to the current text color.
func (res *ThemedResource) Content() []byte {
return svg.Colorize(unwrapResource(res.source).Content(), Color(res.ThemeColorName()))
return colorizeLogError(unwrapResource(res.source).Content(), Color(res.ThemeColorName()))
}

// Error returns a different resource for indicating an error.
Expand Down Expand Up @@ -742,7 +744,7 @@ func (res *InvertedThemedResource) Name() string {
// Content returns the underlying content of the resource adapted to the current background color.
func (res *InvertedThemedResource) Content() []byte {
clr := Color(ColorNameBackground)
return svg.Colorize(unwrapResource(res.source).Content(), clr)
return colorizeLogError(unwrapResource(res.source).Content(), clr)
}

// ThemeColorName returns the fyne.ThemeColorName that is used as foreground color.
Expand Down Expand Up @@ -775,7 +777,7 @@ func (res *ErrorThemedResource) Name() string {

// Content returns the underlying content of the resource adapted to the current background color.
func (res *ErrorThemedResource) Content() []byte {
return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNameError))
return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNameError))
}

// Original returns the underlying resource that this error themed resource was adapted from
Expand Down Expand Up @@ -810,7 +812,7 @@ func (res *PrimaryThemedResource) Name() string {

// Content returns the underlying content of the resource adapted to the current background color.
func (res *PrimaryThemedResource) Content() []byte {
return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNamePrimary))
return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNamePrimary))
}

// Original returns the underlying resource that this primary themed resource was adapted from
Expand Down Expand Up @@ -839,7 +841,7 @@ func (res *DisabledResource) Name() string {

// Content returns the disabled style content of the correct resource for the current theme
func (res *DisabledResource) Content() []byte {
return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNameDisabled))
return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNameDisabled))
}

// ThemeColorName returns the fyne.ThemeColorName that is used as foreground color.
Expand Down Expand Up @@ -1399,3 +1401,11 @@ func unwrapResource(res fyne.Resource) fyne.Resource {
}
}
}

func colorizeLogError(src []byte, clr color.Color) []byte {
content, err := svg.Colorize(src, clr)
if err != nil {
fyne.LogError("", err)
}
return content
}
Loading