Package limn.graphics

Class SvgIcon

java.lang.Object
limn.graphics.SvgIcon
All Implemented Interfaces:
Icon

public final class SvgIcon extends Object implements Icon
A themeable vector Icon: keeps the SVG source and lazily draws it (via the installed SvgRenderer) to a cached Image per requested pixel size (a small LRU of 16 sizes). Because it rasterizes at the exact device size it is drawn at (see Icon.paint(limn.graphics.Canvas, float, float, float, limn.graphics.Color, boolean)), an SVG icon stays crisp on HiDPI and at any zoom: no blur from up/down-sampling a fixed bitmap. Draw it tinted to recolor with the theme (SVG icons are single-color masks, so Icon.tintable() is true); draw it untinted to keep the SVG's own colors.

Each rasterized Image is cached and its identity is the GPU texture key, so reusing one SvgIcon reuses a single texture per size across the whole UI. Drawing needs the backend running (the renderer is installed at startup); call image() during setup to warm the cache if desired.

An SVG of any aspect is drawn fitted into the box it is given and centred, its longer side the box's side, as Icon.paint(limn.graphics.Canvas, float, float, float, limn.graphics.Color, boolean) draws any icon.

A source the renderer refuses draws nothing. A malformed SVG, or one with no size of its own, fails at every size, so the first refusal is remembered, logged once as a warning, and answered from then on with an empty bitmap rather than a parse per paint: an exception thrown from a paint is caught by the frame and counted against the window, and enough of them stop the window repainting at all.

An instance is confined to the UI thread: the size cache is a plain LRU map with no lock, and it is written by every miss. image(int) and image(int, boolean) therefore reject any other thread once a backend is running. The one way to move the parse and rasterize off that thread is imageAsync(int), which does the work on the worker pool and folds the result back in here.

  • Nested Class Summary

    Nested classes/interfaces inherited from interface limn.graphics.Icon

    Icon.Mirroring
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    The system property that sets the rate every animated SVG plays at, unless its icon says.
  • Method Summary

    Modifier and Type
    Method
    Description
    int
     
    int
    frameMillis(int frame)
     
    int
     
    static SvgIcon
    An icon from a classpath resource (e.g.
    float
     
    Base-resolution bitmap, a convenience for warming the cache or ad-hoc draws.
    image(int pixelSize)
    Rasterized bitmap fit into pixelSizepx (cached; stable identity, because that identity is the GPU texture key).
    image(int pixelSize, boolean dark)
    Icon entry point: rasterizes at the requested device size (theme brightness is irrelevant; SVG icons recolor by tint, not by variant).
    image(int pixelSize, boolean dark, int frame)
    One frame of the bitmap Icon.image(int, boolean) answers, which is that method's answer for frame 0.
    imageAsync(int pixelSize)
    Rasterizes for pixelSize on the Ui worker pool and folds the bitmap into this icon's cache on the UI thread, so the next image(int) at that size is a hit and no frame pays for the parse.
    static void
    Installs the backend's renderer (called by the backend at startup).
    boolean
    An SVG is animated when it has SMIL or CSS animation, which the toolkit reads and plays itself, a moment a frame.
    static boolean
    Whether SVG icons can be drawn at all; without a renderer they draw nothing.
    static SvgIcon
    load(Path file)
    An icon from a file.
    int
    One whose animations repeat without end plays without end; one whose all end plays once and rests.
    static SvgIcon
    of(String svg)
    An icon from raw SVG source.
    static void
    Removes the renderer if it is still expected, so a late teardown cannot clear a newer one.
    float
     
    withFrameRate(int framesPerSecond)
    The same SVG, playing at framesPerSecond if it is animated: its own rate, which wins over "limn.svg.frameRate".

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface limn.graphics.Icon

    paint, paint, tintable
  • Field Details

    • FRAME_RATE_PROPERTY

      public static final String FRAME_RATE_PROPERTY
      The system property that sets the rate every animated SVG plays at, unless its icon says.
      See Also:
  • Method Details

    • withFrameRate

      public SvgIcon withFrameRate(int framesPerSecond)
      The same SVG, playing at framesPerSecond if it is animated: its own rate, which wins over "limn.svg.frameRate". An SVG says nothing of a rate, since neither SMIL nor CSS has one: both measure time, and the toolkit draws that time as frames, 24 a second unless set, the rate of film.
      Parameters:
      framesPerSecond - from 1 to 120
      Returns:
      a new icon of the same source
      Throws:
      IllegalArgumentException - when the rate is out of that range
    • frameRate

      public int frameRate()
      Returns:
      the rate it plays at: its own, else the system property's, else 24
    • isAnimated

      public boolean isAnimated()
      An SVG is animated when it has SMIL or CSS animation, which the toolkit reads and plays itself, a moment a frame. Reading it parses the source on the first call.
      Specified by:
      isAnimated in interface Icon
      Returns:
      whether this icon has more than one frame, which a component plays; false unless overridden
    • frameCount

      public int frameCount()
      Specified by:
      frameCount in interface Icon
      Returns:
      how many frames; 1 for a still icon
    • frameMillis

      public int frameMillis(int frame)
      Specified by:
      frameMillis in interface Icon
      Parameters:
      frame - from 0 to Icon.frameCount() - 1
      Returns:
      how long that frame is shown, in milliseconds; 0 for a still icon
    • loopCount

      public int loopCount()
      One whose animations repeat without end plays without end; one whose all end plays once and rests.
      Specified by:
      loopCount in interface Icon
      Returns:
      how many turns the animation makes: 0 for one that does not end, which is also what a still icon answers
    • image

      public Image image(int pixelSize, boolean dark, int frame)
      One frame of the bitmap Icon.image(int, boolean) answers, which is that method's answer for frame 0. A frame of an animation decoded as it plays is decoded here, on the calling thread, if it is not in memory. A frame of an animated SVG is drawn at its moment, fitted into the box; drawn once and kept, for a size whose frames together fit in sixteen megabytes. UI thread.
      Specified by:
      image in interface Icon
      Parameters:
      pixelSize - target extent in physical pixels (≥ 1)
      dark - whether the active theme is dark
      frame - from 0 to Icon.frameCount() - 1
      Returns:
      the frame, of the size frame 0 is
    • of

      public static SvgIcon of(String svg)
      An icon from raw SVG source.
    • fromResource

      public static SvgIcon fromResource(String path)
      An icon from a classpath resource (e.g. "/limn/demo/icons/search.svg").

      Reads the resource on the calling thread and needs no asynchronous form: an icon is a few kilobytes of text, read once from a jar the class loader already has open, and nothing is parsed or rasterized here; the expensive half happens later, in image(int), which does have one.

    • load

      public static SvgIcon load(Path file)
      An icon from a file. A picture the file shows by an address relative to it is read from the file's own folder, or from one inside it, and from nowhere else; no address is ever read from the network.

      Reads the file on the calling thread, as fromResource(java.lang.String) reads a resource: an SVG is text, and the pictures it shows are read when it is first drawn.

      Parameters:
      file - the SVG file
      Returns:
      the icon
      Throws:
      UncheckedIOException - when the file cannot be read
      MalformedContentException - when it is larger than an SVG is read
    • installRenderer

      public static void installRenderer(SvgRenderer newRenderer)
      Installs the backend's renderer (called by the backend at startup).
    • uninstallRenderer

      public static void uninstallRenderer(SvgRenderer expected)
      Removes the renderer if it is still expected, so a late teardown cannot clear a newer one.
    • isRendererInstalled

      public static boolean isRendererInstalled()
      Whether SVG icons can be drawn at all; without a renderer they draw nothing.
    • image

      public Image image(int pixelSize)
      Rasterized bitmap fit into pixelSizepx (cached; stable identity, because that identity is the GPU texture key).

      A miss parses and rasterizes the SVG on the calling thread, and the calling thread is normally the UI thread inside Icon.paint(limn.graphics.Canvas, float, float, float, limn.graphics.Color, boolean), so the first paint at a new content scale, at a new size step, or at each step of a zoom gesture, pays a parse and a rasterize inside that frame. imageAsync(int) is the form that pays it on the worker pool; calling that ahead of the paint (on a scale change, or during setup) is what keeps the frame clean. There is no asynchronous form of Icon.image(int, boolean) itself, and there should not be: a paint asking for a bitmap has to be answered during that paint.

      A source the renderer refuses is answered with an empty bitmap, at this size and at every size not already cached, and is not handed to the renderer again.

      Parameters:
      pixelSize - target extent in device pixels; values below 1 are treated as 1
      Throws:
      IllegalStateException - if called off the UI thread while a backend is running, or if no SvgRenderer is installed
    • imageAsync

      public Work<Image> imageAsync(int pixelSize)
      Rasterizes for pixelSize on the Ui worker pool and folds the bitmap into this icon's cache on the UI thread, so the next image(int) at that size is a hit and no frame pays for the parse. Returned unstarted:
      
       icon.imageAsync(px)
           .onSuccess(bitmap -> requestRepaint())
           .deliverIf(this::isShowing)
           .start();
       

      The delivered Image is the one this icon now has cached for that size, so drawing it and drawing image(int) share a single texture. A size already cached is delivered without rasterizing anything again, but a size still in flight is not: unlike Images.loadShared(java.nio.file.Path), two calls for one size before the first lands rasterize twice, and the second bitmap replaces the first in the cache. Warm a size once, or hold the Job and cancel it before asking again.

      Cancelling stops the delivery, not the work: if the rasterize had already finished, the bitmap is still folded into the cache; it is paid for either way, and the next paint at that size may as well have it. A failure (no installed renderer, malformed SVG) reaches onFailure on the UI thread; nothing is thrown from here. A refused source is remembered as image(int) remembers it, and asking again fails with the same cause without rasterizing.

      Parameters:
      pixelSize - target extent in device pixels; values below 1 are treated as 1
      Throws:
      IllegalStateException - if called off the UI thread, or if no backend is running
    • image

      public Image image(int pixelSize, boolean dark)
      Icon entry point: rasterizes at the requested device size (theme brightness is irrelevant; SVG icons recolor by tint, not by variant). UI thread, with the miss cost described on image(int).
      Specified by:
      image in interface Icon
      Parameters:
      pixelSize - target extent in physical pixels (≥ 1)
      dark - whether the active theme is dark (selects light/dark variants)
    • width

      public float width()
      Returns:
      its own width, in its own units, which the toolkit takes as logical points: what an image view showing it measures at when nothing else sets its size. 0 for a source the renderer refuses. Read on the first call, which opens the document on this thread
      Throws:
      IllegalStateException - if no SvgRenderer is installed
    • height

      public float height()
      Returns:
      its own height; see width()
    • image

      public Image image()
      Base-resolution bitmap, a convenience for warming the cache or ad-hoc draws. UI thread.