weisJ/jsvg

Recoloring ?

Closed this issue · 4 comments

bric3 commented

This is more of a question / discussion on the matter.

I wonder if this is possible or sensible to install a recoloring mechanism ?
I understand that one could leverage some kind of filter via paint / paintIcon methods but I wonder if such modifications could be done before e.g. via SVGDocument API ?

weisJ commented

This is actually already possible (albeit without proper documentation). In fact there are multiple ways to achieve it:

  1. Using a custom PaintParser:
    class CustomColorsParserProvider extends DefaultParserProvider {
        @Override
        public @NotNull PaintParser createPaintParser() {
            return new CustomColorsPaintParser(super.createPaintParser());
        }
    }
    
    class CustomColorsPaintParser implements PaintParser {
        private final PaintParser delegate;
    
        CustomColorsPaintParser(PaintParser delegate) {
            this.delegate = delegate;
        }
    
        @Override
        public @Nullable Color parseColor(@NotNull String value, @NotNull AttributeNode attributeNode) {
            Color c = delegate.parseColor(value, attributeNode);
            // adjust c to your liking
            return c;
        }
    
        @Override
        public @Nullable SVGPaint parsePaint(@Nullable String value, @NotNull AttributeNode attributeNode) {
            SVGPaint paint = delegate.parsePaint(value, attributeNode);
            // Adjust paint. Effectively only AwtSVGPaint needs to be handled here 
            // (Gradients etc are already covered by `parseColor `) 
            return paint;
        }
    }
    Use it at load time new SVGLoader().load(url, new CustomColorsParserProvider()). This will replace the colours once when the svg is loaded. If you want to dynamically change colours then at least for plain colours (e.g. no gradients etc) something like the following could be returned from parsePaint:
    class DynamicAWTSvgPaint implements SimplePaintSVGPaint {
        @Override
        public @NotNull Paint paint() {
            // return whatever color you like
        }  
    }  
    For gradients the second approach allows a more fine-grained control (but with more setup involved).
    Alternatively one could employ something like this: https://github.com/weisJ/darklaf/blob/master/utils/src/main/java/com/github/weisj/darklaf/util/ColorWrapper.java
    To dynamically change any color.
  2. Using a DomProcessor:
    class CustomColorsParserProvider extends DefaultParserProvider {
        @Override
        public @Nullable DomProcessor createPreProcessor() {
            return new CustomColorsProcessor();
        }
    }
    
    class CustomColorsProcessor implements DomProcessor {
    
        @Override
        public void process(@NotNull ParsedElement root) {
            processImpl(root);
            root.children().forEach(this::process);
        }
    
        private void processImpl(ParsedElement element) {
            SVGPaint paint = null;
            SVGNode node = element.node();
            if (node instanceof LinearGradient) {
                // create a custom dynamic paint based on linear gradient
                // Note that at this point element.node() hasn't yet been
                // fully parsed. Parsing its attributes needs to be done manually.
            } else if (node instanceof RadialGradient) {
                
            } // and so on
            
            // Replace the paint associated to node with the new paint
            element.registerNamedElement(node.id(), paint);
        }
    }
    See https://github.com/weisJ/darklaf/blob/master/property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java for an example.

For more complicated adjustments (as in the second option) I have to acknowledge that the API isn't very nice however for simple changes option 1 should work fine. I explicitly don't want to allow changing the Dom after it has been fully parsed.

bric3 commented

Thank you very much. This is really nice! I think option 1 is good enough for my use case.
Also thank you for the link to actual examples.

Documentation has been added.

Thank you very much