12

I'm attempting to display an inline image in a Java JEditorPane. The code below uses HTML content that properly displays the image in Firefox, but not in the JEditorPane. Any ideas why? Thanks.

import javax.swing.*;
import java.awt.*;

public class InlineImage {

    public InlineImage() {
        JFrame frame=new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JEditorPane edit=new JEditorPane();
        frame.getContentPane().add(edit);
        edit.setContentType("text/html");

        String html = "<html><body>Local image<br><img src=\"data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAACeklEQVR42u1bHZBCURgNgiBYCINgIVhYCIKFhSBYCIIgCIKFxSBoZpsJgjAIgmAhCIIgCIKFIAiChSAIF4IgCL7d82abnWl69Xq9+7r1Dhyp93PfOff7ufd+n8/nEyF0AkmgIAQFoSDEjQgSCn1LPD6SbPZDSqWKNBqv0m5nZDh8lsnkUebziIH1OiC/d+wF/tteN50+GPfiGbVaQcrld8nnm8Y78C4K8odAYC3R6Jfkci2pVosGaYtFWDYbvynRKgDx8G4Ij7FgTBjbzQuC2ZhOd4wZCgIOzfBLYysSxooxh8OL2xAEH4KPGo3irs98pwF3CZcXi42vS5CtCPiAaxfBDLPZvRQKNUWW49CDEomBdDrpmxXBDN1uSlKprvj9m8sLgkHAx47HMU+JYObSkBmenxDYvDGTaRum63UhdoFUG9maa4IgW4KZkvzD6PVebMaYEy6GSS6XdyTcIlaroA1rsRgr6vU3zwVsp4BFZzC4ckYQBCmYH4k9D4NBwmLAP2IZFMNZUY6nxwf+rFRKJNJhYLVvSxAs9Bgz1ADcniQIzIprDLVbL+aua8+PyWSfxCkGOLYsSKuVI2mKAY4tC4LlP0lTv8ViWRAS5g4oyLUKQpelmctiUNcsqDPt1Szt5cJQs4Uht0402zrh5qKGm4tb19XvJ0mkq2ciPKC6ngOq3SNcEms/xXXsCJdFDhoWOeyWAdGFWSsDikTm7hXKwVq4VjEvlLNfWnpmKSkqGFlK+l9Kaj1WuFBs7cWKRrgmbYqtvdyOUCxW9W5HOCQOXBobdtjSxpY2J5o+L0W+55o+7bZFN5t5JW3RT0+fbIsmKAgFISgIBSHU4QdCoO0W7Xd4AwAAAABJRU5ErkJggg==\"></body></html>";
        edit.setText(html);

        frame.setSize(500,300);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {new InlineImage();}
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Mike
  • 123
  • 1
  • 4

1 Answers1

18

You need to add a protocol handler for "data:" so an URL/URLConnection can be opened for it. Alternatively you could create some protocol handler "resource:" for class path resources.

You need a package data with a class Handler (fixed name convention!). This will be the factory class for "data:" return an URLConnection. We will create DataConnection for that.

Installing a protocol handler can be done via System.setProperty. Here I provided Handler.install(); to do that in a generic way.

package test1.data;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

public class Handler extends URLStreamHandler {

    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return new DataConnection(u);
    }

    public static void install() {
        String pkgName = Handler.class.getPackage().getName();
        String pkg = pkgName.substring(0, pkgName.lastIndexOf('.'));

        String protocolHandlers = System.getProperty("java.protocol.handler.pkgs", "");
        if (!protocolHandlers.contains(pkg)) {
            if (!protocolHandlers.isEmpty()) {
                protocolHandlers += "|";
            }
            protocolHandlers += pkg;
            System.setProperty("java.protocol.handler.pkgs", protocolHandlers);
        }
    }
}

The URLConnection gives an InputStream to the bytes:

package test1.data;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import javax.xml.bind.DatatypeConverter;

public class DataConnection extends URLConnection {

    public DataConnection(URL u) {
        super(u);
    }

    @Override
    public void connect() throws IOException {
        connected = true;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        String data = url.toString();
        data = data.replaceFirst("^.*;base64,", "");
        System.out.println("Data: " + data);
        byte[] bytes = DatatypeConverter.parseBase64Binary(data);
        return new ByteArrayInputStream(bytes);
    }

}

The clever thing here is to use Base64 decoding of DatatypeConverter in standard Java SE.


P.S.

Nowadays one would use Base64.getEncoder().encode(...).

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • *"The clever thing here"* I saw about 4 things that made me think - Wow, that's clever! My 'learn item(s)' of the day. :) – Andrew Thompson Feb 22 '12 at 09:02
  • 1
    Worked like a charm! Many thanks. Researching your answer did raise one question. I found several references to the fact that the java.protocol.handler.pkgs property is read by the JVM at startup, so the handlers must be provided on the command line (e.g. java -Djava.protocol.handler.pkgs=xxxxxx). But I simply called test1.data.Handler.install() in my main() method and it worked. Has the behavior changed in newer versions of Java, or did I simply read it wrong? – Mike Feb 22 '12 at 23:40
  • 1
    The command line definition `-D` is equivalent to `System.setProperty`. Common sense is *not* to preload `Handler`s (like the rare ftp, https), as this would slow down startup. You probably encountered the alternative, using an URLStreamFactory as extra parameter. So really it very dynamic. – Joop Eggen Feb 23 '12 at 00:09
  • I was hoping that was the case. Thanks again for the help. – Mike Feb 23 '12 at 00:52
  • Fantastic answer, using it to render image's in this jasper report [answer](http://stackoverflow.com/a/41027997/5292302) – Petter Friberg Dec 07 '16 at 22:18
  • **fixed name convention!** : Strange, You will have to take care to keep the class name AND the end of the package name: have to end with **.data** (this is an obscure tip or me, but useful here) – pdem Feb 01 '17 at 15:39
  • 2
    @pdem yes java SE there really chose a baroque solution. Maybe because it is a very old solution, before java SPI I believe. – Joop Eggen Feb 02 '17 at 09:56
  • This one works awesome. Thanks a lot @JoopEggen. It saved a lot of effort for me. – greenhorn Nov 15 '21 at 08:03