-
Notifications
You must be signed in to change notification settings - Fork 855
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
[NETBEANS-2604] Support SVG icon loading from ImageUtilities #1278
Conversation
Finally :) great work :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find anything obviously wrong.
+1
platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
Outdated
Show resolved
Hide resolved
platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
Outdated
Show resolved
Hide resolved
I had a look at the license situation and I have two suggestions. One is, that that "batik-license" is not correct as it is. There are dependencies, that have separate licenses and notice files. For two of these components (commons-io and commons-logging) the work was already done, here the modules were moved and added as dependency. For The other files, license and notice files were created. There were svg files added and these were declared to be Apache-2.0-ASF in the licenseinfo.xml. That is not necessary as svg is just XML and can perfectly hold the license header on its own. The changes are pushed into this branch: https://github.com/matthiasblaesing/netbeans/tree/pr-1278 feel free to pull them or just be inspired. |
I don't seem to be able to add review comments here, so I'll just enumerate them:
The way you'd do that "compiling" would be to load the SVG and paint it into something like this: https://github.com/timboudreau/imagine/blob/master/Imagine/vector/src/main/java/net/java/dev/imagine/api/vector/painting/VectorWrapperGraphics.java - and then have a simple way to serialize the list of objects that represent paint operations to disk; and load those at runtime. Or perhaps Batik's internal representation could be used in some tighter, more lightweight serialized form (but I doubt it, and it would be nice, under normal circumstances, not to load Batik at all). |
@matthiasblaesing Thanks! I looked through your first patch and added it to the commits in this PR. As for SVG files, at least these particular ones, I prefer to regard them as opaque files that shouldn't be hand-edited, since they will be re-generated by Adobe Illustrator every time we make changes to the icons. (Besides, SVG icons, which are loaded at runtime, will become about 25% larger on average if we add a license comment to each. Not sure it really matters.) |
@timboudreau I did a PNG vs. SVG loading benchmark here: https://gist.github.com/eirikbakke/6519b3e623c0f4703ee82388d1587f08 . Results: Average size of SVG icon file is 2959 bytes (max was 5244 bytes) So loading an SVG image is about 8 times slower than loading a PNG. Though it's still only about 2 milliseconds per icon, so delays shouldn't be noticeable until we've redrawn at least 100 icons. Also note that for HiDPI screens, the PNG file will have to be loaded twice, one at the standard resolution, and one at twice the resolution (4x the number of pixels). In this scenario, I estimate that the PNG approach is only about 4x faster than the SVG approach. I kind of like the store-PNG-in-cache-dir approach--saves us from having to bundle generated PNG files. I'm more sceptical of the "store as log of Graphics2D operations"--that's a lot of code and complexity for a small performance enhancement. Once this PR is merged, I'll add a JIRA ticket reminding us to think about SVG caching in the future. |
@timboudreau On the RenderingHints comment: I believe subpixel rendering is only relevant for text rendering. And there should be no text content in the SVG files that we use for icons in any case (glyphs that appear as part of icon shapes are typically outlined to bezier curves in the drawing program before export to SVG, since the fonts available on the target machine is unpredictable). For SVG icons, I'd rather keep the RenderingHints standardized and unaffected by the environment, to keep the rendering as close as possible to what the icon designer will see in their drawing program (e.g. when turning on Adobe Illustrator's "pixel preview"). That has the added benefit of keeping the cache of rendered images small. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have nothing against loading SVG icons in ImageUtilities
, but I am concerned about making NetBeans Platform too heavyweight. I don't want to see any new JAR files in netbeans/platform/lib/
directory.
If there is no easier way to load SVG icons in Java than to use fifteen batik libraries, then it has to be done as an optional module(s) and be end up being placed in netbeans/platform/modules
, in my opinion.
There is nothing holy about the platform/lib folder? macOS and Windows are moving to HiDPI screens. Most users seem to be on Windows so it makes sense to handle some sort of vectorial icons. As a desktop platform, NetBeans Platform must support vectorial icons out of the box -- any modern app would need it. This does not seem something optional, it's just getting to current-day configurations. Anybody that wants a slimmed down Platform can probably easily recompile it and remove some modules. I'm curious which apps use the platform and would use a modern Apache NetBeans Platform version but wouldn't want to support HiDPI screens? |
No, but you assume that everyone would want to approach it in this way, as opposed to icon fonts or responsive bitmaps, both of which have the benefit of relying solely on core Java desktop support. It is also feasible to have a lightweight, single file, SVG renderer that would be enough for this - Processing has one, but not under a usable license. I would still prefer to see some sort of generic IconLoader SPI (and similar for branding text) such that we allow for IDE modules and RCP apps to use / experiment with alternative approaches that work for them. |
The good news is that the work is now done for all of these different approaches to work, in the PR that preceded this one ( #1273 ). In fact, my own NetBeans Platform application uses both SVG icons as well as a custom Icon implementation based on an icon font (for a certain subset of icons). And openide.util.ui also has an abstract base class called VectorIcon now, which can be used for custom-painted HiDPI icons (used for window system LAF icons in #859 ).
See my answer to "Why not a generic IconLoader SPI and first loader that handles the URL wins?" on the mailing list: I think the IconLoader SPI would be a separate feature that could be added later. The main reason for the SVGLoader SPI is to delay loading of the Batik SVG library JARs until after the core startup modules have loaded (e.g. the splash screen). In fact, I'd love for the SVGLoader SPI to be a non-public implementation detail--is there a way I can do this? In your proposed IconLoader SPI, how do you image that implementation modules will specify which URLs they can handle? If the implementation modules must do this programmatically, then they cannot be loaded lazily. So in that case the IconLoader SPI ends up being separate from the SVGLoader SPI in any case. Any thoughts on this? I did do one change to this PR, though: I ditched the feature of automatically loading an SVG file if a PNG file with the same extension is requested (e.g. loading "icon.svg" when "icon.png" is requested). That kind of substitution is something that could be implemented in the proposed future IconLoader SPI. |
Sorry, got waylaid by travels and forgot I hadn't replied there.
Ideally it would be declarative where possible, but probably not viable in this case. Personally I'd move SVGLoader and the Batik implementation outside of ImageUtilities, and have the former lazily load the latter. OTOH, maybe should just be reusing the ImageIO infrastructure for this anyway? |
@neilcsmith-net The IconLoader SPI sounds simple enough. It's just an extra level of indirection. If people want it I can make it part of this commit. ImageIO is not the best interface to use here, because we need to load javax.swing.Icon instances, not java.awt.Image.
Is something like this what you have in mind?:
|
Just a thought having noticed there was a plugin for Batik. It looks like you're always using cached Images? Therefore it might be viable to use the ImageIO sized rendering support for creating the cached sizes without requiring a specific additional SPI at all. The HiDPIIcon could then theoretically be non-abstract.
That's what I meant, but I might have badly worded it! The IconLoader interface is pretty much what I had in mind. Or anything similar that allows additional overrides and/or removes the filetype logic from inside ImageUtilities itself. I'll shut up and let anyone else agree or disagree with that point now! 😄 Other than that I think this is a great step forward. |
No, that's good--it just means we're on the same page! :-) I'll let the ideas simmer for a bit, and see if I get an answer to the other question (how to make a module actually optional). Then I can have a look at the IconLoader approach. |
There are actually quite smart ideas! I just think that after 6 years since the Retina display was announced any kind of support for HiDPI screen is good. If we want to provide a HiDPI SPI so we have multiple solutions that would be even better. But remember that today we have no solution. As far as I can tell this code doesn't introduce public API (except the .svg icon contract) so besides the JAR bloat, it's an internal feature. I dislike bloat, but remember a full NetBeans build is 1GB! It would also make sense that when a 2nd equally good HiDPI icons solution arrives, then we take the time to invent a SPI and have both solutions implementat that SPI, etc. Anyhow, Eirik is free to do however he decides, I reviewed the patches, they looked OK to me. |
There are three kinds of
To benefit from enhanced classloading features like compatibility patching or fast startup, then it is essential to avoid placing JARs into
Maybe, if desktop is your only focus, but ...
... we are talking about the NetBeans Runtime Container. People like @jlahoda, @tzezula and @sdedic spent a significant amount of time making sure NetBeans modules can be used in a headless mode. We are not going to ruin such efforts just because we want SVG icons.
That would seem OK from the runtime container point of view. However expect troubles at early stages of startup - for example you couldn't use SVG to render splash screen... Good luck! |
Am Donnerstag, den 06.06.2019, 15:38 -0700 schrieb Eirik Bakke:
As for SVG files, at least these particular ones, I prefer to regard
them as opaque files that shouldn't be hand-edited, since they will
be re-generated by Adobe Illustrator every time we make changes to
the icons.
This raises the question whether Adobe Illustrator is the right tool. I
don't know the ratio of people having access to Adobe Illustrator, but
I would prefer tools directly working on the SVG code.
If needed for example with pure SVG code, we could transform the files
at build time and strip comments and so on.
However, Apache is pretty clear, that files, that can carry the license
header should (very hard should) carry it. For SVG files this is the
case. If Adobe Illustrator is not able to add a fixed comment
(apparantly it can, as there is already one), additional tooling needs
to do it (adding an XML comment will not be hard).
(Besides, SVG icons, which are loaded at runtime, will become about
25% larger on average if we add a license comment to each. Not sure
it really matters.)
Depends - I tested the raw parsing speed (file -> DOM) and got a
penalty of about 5% (undo.svg with and without comment). For
transcoding to PNG I got a penalty of 4%. With caching this is a one-
off problem. Seinng the SVG-PNG comparison, this speed problem looks
not significant.
The size itself IMHO is not problem - if it is, run the files through
gzip and decompress von the fly.
Am Samstag, den 08.06.2019, 20:41 -0700 schrieb Jaroslav Tulach:
> I'm curious which apps use the platform and would use a modern
> Apache NetBeans Platform
> version but wouldn't want to support HiDPI screens?
... we are talking about the NetBeans Runtime Container. People like
@jlahoda, @tzezula and @sdedic spent a significant amount of time
making sure NetBeans modules can be used in a headless mode. We are
not going to ruin such efforts just because we want SVG icons.
That arguement does not cut it. There is net.java.html.* already in the
platform. The same argument would cut there, as they are surely not
running headless.
|
Except net.java.html.* is not in the NetBeans Runtime Container. Not sure if lacking SVG for the splash screen is much of a problem? And there was another discussion about changing splash implementation recently. I had a quick look for lighter weight SVG options under suitable licenses - nothing in just one class, but did find https://github.com/blackears/svgSalamander Haven't tried it! |
Am Sonntag, den 09.06.2019, 10:01 -0700 schrieb Neil C Smith:
I had a quick look for lighter weight SVG options under suitable
licenses - nothing in just one class, but did find
https://github.com/blackears/svgSalamander Haven't tried it!
I only had a quick look at it in the past. At that time it lacked CSS
base font support. My gut feeling is, that there is a reason, that
batik has the size it does.
|
Yes, but I'm going on the basis that's probably a lot of features not needed for icons? Of course, another option might be compile-time class generation from SVG using Photon from https://github.com/kirill-grouchnikov/radiance ? Closer to @timboudreau suggestion above? |
I'll make sure to put Batik support in an optional module so it's not part of the core runtime container. If it's required, I'm fine with putting licenses in the SVG files. (Alternatively we could use SVGZ instead, which is just a gzipped SVG file--clearly a binary file...) The tool to use for drawing icons is really up to whoever volunteers their time or money to draw them. Though ideally we want people with some graphic design experience to draw icons--and among those people Adobe Illustrator is pretty standard. |
Yes, the proposed HiDPI splash screen code takes another approach for this, using PNG instead. (See separate PR at #1246 . The ScaledBitmapIcon class in the latter PR will be simplified a bit once this PR is merged and CachedHiDPIIcon becomes a public class that it can extend from.) |
Am Sonntag, den 09.06.2019, 12:05 -0700 schrieb Eirik Bakke:
If it's required, I'm fine with putting licenses in the SVG files.
(Alternatively we could use SVGZ instead, which is just a gzipped SVG
file--clearly a binary file...)
We wan't to be good citizens of the Apache eco system, we should not
unnessarily stretch the rules (a nitpicker would tell you, that you can
gzip at build time ...).
|
There's a remaining test case failing which may or may not be related to the changes here:
I'll look at this one after rebasing on top of master, in case the failure goes away then. |
platform/openide.util.ui.svg/src/org/openide/util/svg/SVGIcon.java
Outdated
Show resolved
Hide resolved
platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
Outdated
Show resolved
Hide resolved
On one hand, I sort of like the separation - the current platform's use-case is to read SVG, not generate. So in this sense, you're including the minimum necessary to satisfy the requirement. But if you, for some reason, include full batik, why not distribute @jtulach what's the architect's view on including more-than-required from the library ? |
@sdedic Yes, batik-all would be an option as well. That one is 50% larger (4160KB) than the current subset of JARs. |
43effd8
to
708c894
Compare
Following up on comment I made way earlier, but having just looked into #1652 it's interesting to see they have svgSalamander based icon support - https://github.com/JFormDesigner/FlatLaf/blob/master/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java |
@neilcsmith-net Yeah, svgSalamander is a much smaller library. Presumably it implements a smaller subset of SVG features than Batik. The SPI introduced in this PR would make it possible to switch out the Batik-based module with a svgSalamander-based one, in case anyone wants to try this at a later point. (In FlatLaf, it looks like FlatSVGIcon is only used from the DemoFrame, not in the LAF itself. The SVG files used are all very simple ones.) |
Dne čt 21. 11. 2019 19:52 uživatel Svatopluk Dedic <notifications@github.com>
napsal:
@sdedic <https://github.com/sdedic> An alternative here would be to
actually include *all* of the Batik library JARs--we're already including
most of them. This would increase the total size of Batik JARs from 2.7MB
to 3.6MB (a 32% increase). Thoughts?
On one hand, I sort of like the separation - the current platform's
use-case is to *read* SVG, not generate. So in this sense, you're
including the minimum necessary to satisfy the requirement.
But if you, for some reason, include full batik, why not distribute
batik-all
<https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-all> ?
@jtulach <https://github.com/jtulach> what's the architect's view on
including more-than-required from the library ?
I can find arguments for both directions. Which one do you want? ;-)
We want vector icons in the platform. Clearly that leads to Batik. Include
it as 3rd party lib (no friend deps). Make it optional - e.g. a module.
That's all I want for now.
-jt
PS: Later I'd also like to avoid startup slowdown expected by the need to
load XML parser and parse the .svg files.
|
164c84e
to
67e77c7
Compare
I'll go with the smaller batik library subset approach then. |
Force-pushing made some earlier reviews unavailable, but the requested changes have been taken into account, so marking these reviews as "dismissed" now. |
Jaroslav's review is resolved--the SVG loader module is optional and new JARs are confirmed not to end up in platform/lib. Have to use the "dismiss review" feature since earlier force-pushing made the previous comments unavailable to "resolve."
There seems to be some transient Travis failures... I had it all green yesterday. Will wait until I can make it pass again before merging. |
Two subsequent Travis builds, on the same unchanged codebase, yielded one transient test failure each, but in different test jobs. https://travis-ci.org/apache/netbeans/builds/616390107 So I take this to mean that all tests have passed... (since each of the failed jobs, "Test ide modules" and "Test platform modules, Batch 1", passed once in the other Travis run) |
a3a5a22
to
fc03571
Compare
As a part of the effort to make NetBeans look better on HiDPI displays, the ImageUtilities class has now been completely updated to support scalable implementations of the java.awt.Icon interface, and to support loading of icons and images from SVG files. If an SVG file resource exists with the same base name as an existing bitmap icon, and the SVG loader implementation modules is installed, the SVG file will be loaded instead (e.g. "icon.svg" will be loaded instead of "icon.png"). SVG file resources can also be loaded explicitly. Details: * Have ImageUtilities support SVG image loading via an optional pluggable service provider, and add an implementation based on the Batik SVG library. The latter is enabled by default in the "platform" cluster. * Add SVG versions of the Undo/Redo action icons (visible in the Edit menu and in the NetBeans toolbar in "Small Toolbar Icons" mode). This will serve to test SVG icon loading and painting. * Moved the o.apache.commons.io and o.apache.commons.logging modules from the "ide" cluster and into the "platform" cluster, as Batik depends on these libraries. * Refactor, clean up, and generalize ImageUtilities.getLoader; it is used to fetch and cache both the ClassLoader and the SVGLoader implementation. The SVG loader implementation module will only be loaded once an actual SVG file is found and needs to be loaded.
Details: * Use the shared caching logic for scaled HiDPI image variants now available by subclassing from org.openide.util.CachedHiDPIIcon. * Properly wait for the image to load before scaling (via ImageIcon's MediaTracker). * Rename ScaledBitmapIcon.width/height to sourceWidth/sourceHeight. * Document some known image scaling issues on Java.
fc03571
to
48dc68f
Compare
Spurious Travis failures are somewhat par for the course - you can retrigger them from the Travis UI assuming your accounts are connected? Speaking of which, why do the commits show as ultorg rather than you personally? Author email should ideally be your Apache one. |
It's my real name and email in the git logs, but it didn't match the email that was associated with this github account. Now fixed to avoid confusion. (I prefer to use a real email address over the Apache one, to avoid a proliferation of email addresses that I have to check or maintain...)
|
The name shown on github is mostly useless, as github tries to be clever. If you need the real information, only |
I did a manual squash, saw no failing Travis tests, and merged. I also tested a private build of the platform with my NetBeans Platform app, which makes extensive use of SVG icon loading. Added the following issues for the future: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are some post mortem API related notes.
|
||
/** | ||
* SVG image loader. This is an optional service provider. If implemented, a single instance should | ||
* be placed in the default lookup (e.g. via the {@link ServiceProvider} annotation). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing @since
annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add this in a separate commit.
@@ -35,7 +38,7 @@ | |||
* representations for HiDPI displays. Bitmaps for multiple HiDPI scaling factors can be cached at | |||
* the same time, e.g. for multi-monitor setups. Thread-safe. | |||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly visible class should also get @since
annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add it.
* @param deviceWidth the required width of the image, in device pixels | ||
* @param deviceHeight the required height of the image, in device pixels | ||
* @return an image compatible with the given parameters | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected static
methods seem slightly suspicious to me. Who's supposed to call them? Any subclass? But in fact everyone can do abstract class MyDummySubclass extends CachedHiDPIIcon { /* now call the method */ }
and get access to that method. E.g. it doesn't provide any encapsulation over public static
.
I am just stating that for your consideration. Maybe using protected static
here is correct...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making it "protected" mainly makes it clear, from a documentation perspective, that it's a method intended for use by implementors of CachedHiDPIIcon, rather than clients of it.
Who's supposed to call them? Any subclass?
Yes. If they want. It's just a way to show and document to implementors how to create a suitable backbuffer.
But in fact everyone can do abstract class MyDummySubclass extends CachedHiDPIIcon { /* now call the method */ } and get access to that method.
That's fine. It's static, so it doesn't actually expose any object state.
This PR adds support for SVG icon loading from ImageUtilities, and tests it by adding SVG icons for the Undo and Redo actions. See https://issues.apache.org/jira/browse/NETBEANS-2604 and the attached screenshot. This PR depends on #1273, which must be applied first. This PR can be reviewed more or less independently (just ignore the first commit, which contains all the changes from the other PR).
Some questions for more experienced NetBeans veterans:
Thanks for your help!
Example SVG icons loaded and displayed in the IDE:
Example renderings of other SVG files tested (from Ultorg, an application built on the NetBeans Platform), as displayed using the platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java utility (which was added in an earlier commit):