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 rasterizes it (via the installed SvgRasterizer) 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. Rasterization needs the backend running (the rasterizer is installed at startup); call image() during setup to warm the cache if desired.

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
  • Method Summary

    Modifier and Type
    Method
    Description
    static SvgIcon
    An icon from a classpath resource (e.g.
    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).
    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 rasterizer (called by the backend at startup).
    static boolean
    Whether SVG icons can be rasterized at all; without one they draw nothing.
    static SvgIcon
    of(String svg)
    An icon from raw SVG source.
    static void
    Removes the rasterizer if it is still expected, so a late teardown cannot clear a newer one.

    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
  • Method Details

    • 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.

    • installRasterizer

      public static void installRasterizer(SvgRasterizer newRasterizer)
      Installs the backend rasterizer (called by the backend at startup).
    • uninstallRasterizer

      public static void uninstallRasterizer(SvgRasterizer expected)
      Removes the rasterizer if it is still expected, so a late teardown cannot clear a newer one.
    • isRasterizerInstalled

      public static boolean isRasterizerInstalled()
      Whether SVG icons can be rasterized at all; without one 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.

      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 SvgRasterizer 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::isAttached)
           .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 rasterizer, malformed SVG) reaches onFailure on the UI thread; nothing is thrown from here.

      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)
    • image

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