Similar to Android feature related to vector drawables, where the developer can change the color of the asset at runtime through android:tint or by applying a style or theme.
In Android the color is applied over areas with color #000000.
This is a common procedure in Android to allow the use of only 1 asset and provide branding through different screens, or change color according to state computed dynamically (e.g. a wifi or toggle icon could have a grey color when disabled and brand color when active).
This should be pretty doable. Do you have any more documentation on how the tinting works in Android? is it really just swap black for brand color, or is there hue shifting or alpha support?
I'll try finding docs for supporting what we end up doing, but that's it, just swapping black for brand.
I assume that designers provide me with a single color image, which is very common and no manual or runtime changes are necessary. In the occasions they send me 2 assets with 2 different colors I pick one and change it to black, discard the other, then override with code.
Hue shifting is not desirable, and so far I don't see any use for alpha.
I already tried a POC on a fork, looks good.https://github.com/fmatosqg/flutter_svg/commits/colored_theme
I've unfortunately significantly refactored the code you were working from, but I've also added some support for Android VectorDrawables. Take a look and see if you can still make sense of it.
I wonder a little bit about how much sense this will make outside of simple single-color Icons, and if it's something we can make sense of beyond that. I also wonder if it will be confusing coming from Android where you cando this kind of thing based on the state of a widget (e.g. a selected color, disabled color, etc). It seems like it'd be nice to handle that kind of thing.
I think what would be more helpful were if it was possible to define color replacements as strings
I think that'd be interesting, something like a Map
It might also be interesting to be able to just hue shift the colors, or make them completely greyscale.
How about a callback that gives the color about to be rendered and returns the color to actually render? Maybe with some information about whether it's a stroke or fill or gradient etc.
I'm also wondering if there's a way to do this so you can modify more than just the color (perhaps add a transform for example) before drawing, dynamically at paint time to enable animations.
I could see a path where you just modify the colors once at parse time, and a path where you modify them at paint time.
For Animations or color changes depending on some other data change it has to be updated when it paints I guess
Think of the possibilities using gradients and animate the colors
Yes - the idea you'd get called before draw gets called to see if you want to override it. But I'd like it to happen in a way that stays fairly stable and survives some potential changes the code still needs to better support property inheritance/currently unimpelmented SVG features
Some simple API suggestion for parse time:
final Widget svg = new SvgImage.asset(
assetName,
new Size(100.0, 100.0),
filter: filter,
);
And then, filter implements SvgColorFilter:
abstract class SvgColorFilter {
Color replaceColor(Color original);
}
Ok, initial implementation here: fa90a27
Would appreciate some feedback. It may also be nice to just simply provide an isIcon parameter and have some default wiring up to use the iconTheme directly.
Api looks like this:
final ColorReplacer colorReplacer = (Color original) {
if (color.value == 0xFF000000) { // replace black, leave others as is
return Theme.of(context).iconTheme.color;
}
return original;
};
final Widget svg = new SvgImage.asset(
assetName,
new Size(100.0, 100.0),
colorReplacer: colorReplacer,
);
@marcglasberg I'm not using an abstract class for the filter, although that may eventually be a good idea. I think applying other filters will end up requiring a more sophtisticated interface though.
Sounds nice, is ColorReplacer going to be part of the package? I'd suggest the field name staying as it is and the class name / global object having the word "icon" somewhere.
typedef Color ColorReplacer(Color original);
The thing is, this could do more than just icons - it could replace any color that's parsed from the image. It really makes the most sense for single colored icons (or maybe icons with just a few colors), but it could change any color the way it's currently implemented.
(Yes, it's part of the package).
It's sounding to me like what would be most helpful is something like an SvgImageIcon corresponding to the existing ImageIcon.
At one point I was looking at making something in here conform to ImageProvider. Either that or make something that works like ImageProvider. I'll take a look at that avenue as well some more.
Implementing an ImageProvider interface is looking _much_ more promising. It would enable you to do something like this:
const ImageIcon(
const SvgExactAssetImage(
'assets/deborah_ufw/new-action-expander.svg', const Size(100.0, 100.0)),
color: Colors.red,
),
It would also enable you to use SVG images anywhere the framework would expect to see an Image or ImageIcon and take advantage of the framework's image caching capabilities....
That is an awesome approach
One problem I'm running into is that by default the paint used by Skia is not antialiasing. Testing some tweaks to engine, may end up raising a PR to allow specifying what paint to use in Picture.toImage() (and maybe the similar calls for Scene). However, that would require waiting for a later version of Flutter to support this.
There is no problem waiting for a later version of Flutter, that approach is really awesome. However, please still allow for that simple colorReplacer: colorReplacer which we can use immediately for icons. If we want to convert all colors (for monochromatic icons) to 2 different colors representing selected/unselected we can simply do something like colorReplacer: (color) => isSelected ? Colors.white : Colors.green, instead of having two sets of SVG files.
Yes dynamic color replacement should be possible
The ImageProvider approach would be preferable and still allow for easily replacing colors.
I've opened an issue https://github.com/flutter/flutter/issues/17782 - having some trouble tracking down where exactly the degradation is coming from at this point.
I hand edited this snippet to show how it would look like when bringing in colors from theme. Note that this kind of verbosity also happens when applying .display1 or .body2 to Text. Note also that I didn't have the chance to type in an IDE and try this in a real app.
const ImageIcon(
const SvgExactAssetImage(
'assets/deborah_ufw/new-action-expander.svg', const Size(100.0, 100.0)),
color: Theme.of(context)
.iconTheme
.iconColor,
),
I just realized that the Icon theme also specifies size. I know it's out of scope for this particular issue but it was something that I haven't noticed before. At least in my opinion that's one more piece of information I'd like to set at a theme level instead of widget tree.
Other than that, I think this approach is quite good in the sense that it will cover the 80/20 rule.
IconImage defaults to the theme's icon color if none is specified
Here's where I'm at at this point:
color and colorBlendMode, and then applying it to all Paints as a ColorFilter. That's how ImageIcon works under the hood anyway, and would be much clearer here.Dan, we've been having some "blinking" issues. We have substituted some png images with svg, and then it sometimes blinks when redrawing, to a point where we are considering reverting back to png. Without having looked at the code, is it the case it does async work repeatedly, recalculating the image all the time? You said above that "It would also enable you to use SVG images anywhere the framework would expect to see an Image or ImageIcon and take advantage of the framework's image caching capabilities.". It seems to me that caching capabilities are a must have. If you don't do it by yourself by implementing ImageProvider, what's the recommended way of implementing some caching to preventing this blinking? I believe that some cahcing that allows for "blinking prevention" should be included in the documentation. Also, why do you say ImageProvider could be misused perf wise? Isn't the blinking a sign of performance problems (seems to be recalculating the image).
@marcglasberg - can you open a separate issue for that, and possibly attach either code to reproduce it or a screen recording of the behavior?
I have some ideas about what might be causing that, and a potential improvement that could make it better, but would appreciate more details.
I should try to isolate the problem with some code to reproduce it.
Color filtering is now implemented on Master. It is not currently defaulting to IconTheme colors, maybe it'd be nice to create a SvgIcon widget to do that.
The interface looks like:
new SvgPicture.asset(
'icon.svg',
color: Colors.blueGrey,
),
It also can take a BlendMode, which defaults to srcIn (same as the way it works on Image in Flutter). This will properly handle multiple colors without the developer needing to know all of them ahead of time, and can also handle alternative blending modes if desired.
Released in v0.3.1 - I feel confident about the public API around this at least, internal implementation might change slightly over time.
A caching bug around this was reported by @marcglasberg - it should be fixed in master, once that's verified probabily will do another patch release for it.
Hi Dan, I just tried your fix and it works.
Released v0.3.2 with fix
Most helpful comment
Implementing an
ImageProviderinterface is looking _much_ more promising. It would enable you to do something like this:It would also enable you to use SVG images anywhere the framework would expect to see an
ImageorImageIconand take advantage of the framework's image caching capabilities....