Search code examples
javahtmljtextpane

Display base64-encoded image


I have a problem with the integration of an image in a text/html JTextPane. The JTextPane is initialized with the following text:

<html>
  <head>
    <style type="text/css">
    </style>
  </head>
  <body>
  </body>
</html>

I insert text with:

kit.insertHTML(doc, doc.getLength(), "<b>" + string + "</b><br>" , 0, 0, HTML.Tag.B);

All the text inserted this way is displayed correctly, but when I tried inserting a base64-encoded image with:

kit.insertHTML(doc,doc.getLength(), "<img src=\"data:image/jpeg;base64," + base64Code + "\"/>", 0, 0, HTML.Tag.IMG);

I only got a placeholder image. When trying with a normal source path, it worked. However, getting the base64 code online and using that got me a placeholder image too, while the exact same code worked on w3school.com's HTML tryit editor.


Solution

  • When a JTextPane sees an <img> tag, it will check if the image exists in a cache, and if not, it will try to read the image from the url. The html library used by JTextPane does not support base64 encoded image data in the <img> tag, so we will need to do it in a different way.

    It turns out that we can manually add images to the image cache. This can be utilized to pick some otherwise invalid url and assign it an image.


    Let's add the image to the cache and show it in a JTextPane!

    First you want to convert the image into a BufferedImage. This can be done using the ImageIO class.

    byte[] imgBytes = decodeBase64(base64Code);
    BufferedImage img = ImageIO.read(new ByteArrayInputStream(imgBytes));
    

    Note that here we need the raw image bytes, not the base64 encoding. If you are reading the image from a file, you can pass a File to the read function instead of the input stream.


    Now that we have the image as a BufferedImage, we can write a function that adds it to the cache.

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static String saveImageToCache(JTextPane pane, BufferedImage img, String name) throws MalformedURLException {
        Dictionary cache = (Dictionary) pane.getDocument().getProperty("imageCache");
        if (cache == null) {
            // No cache exists, so create a new one.
            cache = new Hashtable();
            pane.getDocument().putProperty("imageCache", cache);
        }
        String url = "http:\\buffered/" + name;
        cache.put(new URL(url), img);
        return url;
    }
    

    Note that I suppress some warnings about type parameters on Dictionary and Hashtable. Normally this should be avoided, but in this case we are dealing with Swing nonsense in a way where it's ok to suppress the warnings.

    This method essentially picks some invalid url and stores the image at that url.

    Notice the name argument. This will be part of the url, and if you try to store an image to the cache with the same name as a previous image, this will replace that previous image. Avoid using crazy characters in this name, as new Url(url) may throw a MalformedURLException if it is not a valid url.


    We can now use it with JTextPane.

    BufferedImage img = ...;
    
    JTextPane pane = new JTextPane();
    pane.setContentType("text/html");
    
    String url = saveImageToCache(pane, img, "image1");
    
    pane.setText("<html><body><img src=\"" + url + "\"></body></html>");
    
    JFrame frame = new JFrame("image test");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(pane);
    frame.setSize(img.getWidth(), img.getHeight());
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    

    Note that you must call setContentType before adding the image to the cache, as the method clears the cache. Furthermore it is important that an image is added to the cache before setText is called, to ensure that images are added before swing needs it.

    If the image in the cache is changed by using saveImageToCache with a previously known name, you will need to update the JTextPane in some way, such as calling setText.


    If you have a lot of images, you might want to remove them from the cache when they are no longer needed, in order to avoid excessive memory usage. One way to do this would be to define a function as the one below, which removes the image from the cache.

    @SuppressWarnings({ "rawtypes" })
    public static void removeImageFromCache(JTextPane pane, String name) throws MalformedURLException {
        Dictionary cache = (Dictionary) pane.getDocument().getProperty("imageCache");
        if (cache == null) {
            // There is no cache, so the image is not in the cache.
            return;
        }
        String url = "http:\\buffered/" + name;
        cache.remove(new URL(url));
    }
    

    You can also clear the cache by calling setContentType or by replacing the JTextPane with a new object. This works as the cache is stored in the JTextPane.