简体   繁体   English

如何使用html在swing组件上渲染内部图像?

[英]How to render a internal image on a swing component using html?

I have the following code: 我有以下代码:

public static void main(String[] args) {
    JFrame frm = new JFrame();
    JEditorPane pane = new JEditorPane("text/html", "<html><body>test<br><img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuklEQVR42sWVv09TURTH71AXE40xaRdXEwbD4ODiIFr6EwcXN1cnR0cXoyTWUjcH3n0dDG4aEwcaoqggWn8ERWtoKdVBoA2lxbj4BxzPt+eaPEn7nhDeleRDTr733vP9vtvXU0VE6n8i//79L5KdUKVUXtHobUXxnIAaGtawZzcNdxvgEIw2u0Va39SMa9A9DWvYE2aAKJ4WhpPTipySgBoa1rAnzAAxXPnalkvOjCL3iYAaGtawJ/QA650iaTYuPhVQQ7MbAOazAmqrAdb4hXOe8fW/EFBDsxdgmwM8Z/M5ATU0ewF+cACYvxRQQ7MS4NwtRd9xA/NsviCghoa1vQaIjP2ZcLnBxNng9E1F37p3yYH5awE1NKxhj18PeIx5J6YJ0Jtwaz/vUKObZwoDqW8VaL5xkfQr/gaUBdTQsOZ3Fr3h8dfENAGiCU5X79ygSW7oBKDf8JO/ZXMDamhB59AbHomcZ2KaADGIy+1r5HAjjabv+lMMYNA59ERveCS8L6s3QKV9lRxsfs+H9hn0RG94DAyw1L5CepEPfOCn8cFd7I/vGQa94dE3QJpfjMr2ZZr6oui+D/c+c8OP0tALNKz5nUVveKTzfQKcL6hfmQnVWxxEin9uR8YVlVuXSC+xsQE1NKxhj18PeCSuq/LOl/AIc5KJM6M+ZHB9C60LpD+xuQE1NHO1mYAeI8yJnV/DAyZELIChJJvMtVKkK2xuQA0tKQGGAnpEjXlkT6M4xSazzTPk8ufpBVrKxm8BTGaap8itsvGyoSqatQCljWFya2LcoyaatQCPW8dJr/BnXzOsiGYtwKPmMdKrbFw3rIpmJUCaTR60jlLxK199Q0ANLW0jQJaHycPOQZpq8mTbMDRFy+bDDxDNjKsqjPC0XqAlvRMupACHmWHmbOCECylAxISIBk64kALs+99vkl5XSNCDZhMAAAAASUVORK5CYII=\"></body></html>");
    pane.setEditable(false);
    frm.getRootPane().setContentPane(pane);
    frm.pack();
    frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frm.setVisible(true);
}

What I'm trying to do is render a image on the JEditorPane using base64 codification to store the image. 我要做的是使用base64编码在JEditorPane上渲染图像来存储图像。 It does not need to be base64, but that's as far as I got while trying to render HTML content on the JEditorPane, but I need to use an image that is on a BufferedImage(It is generated by the application.), and, would really prefer not to have to save the image to a file in the hard disk. 它不需要是base64,但这是我在尝试在JEditorPane上呈现HTML内容时所获得的,但我需要使用BufferedImage上的图像(它由应用程序生成),并且,我真的不想将图像保存到硬盘中的文件中。

Can I, somehow, display an BufferedImage in a swing component along with HTML(displaying it in the place given by the IMG tag - the html code is generated by the application too)? 我可以以某种方式在swing组件中显示BufferedImage以及HTML(在IMG标签给出的位置显示它 - html代码也由应用程序生成)?

I had to rewrite one of the Java classes and extend another, but got the BASE64 image working under HTML on the JEditorPane. 我不得不重写其中一个Java类并扩展另一个,但是在JEditorPane的HTML下使BASE64图像工作。 I'll post my solution in case someone needs it in the future... 我会发布我的解决方案以防将来有人需要它...

First, create a Special HTMLEditorKit that will use the rewritten ImageView class for the HTML.Tag.IMG 首先,创建一个特殊的HTMLEditorKit,它将使用重写的ImageView类作为HTML.Tag.IMG

class BASE64HTMLEditorKit extends HTMLEditorKit {

    private static HTMLFactory factory = null;

    @Override
    public ViewFactory getViewFactory() {
        if (factory == null) {
            factory = new HTMLFactory() {

                @Override
                public View create(Element elem) {
                    AttributeSet attrs = elem.getAttributes();
                    Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
                    Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
                    if (o instanceof HTML.Tag) {
                        HTML.Tag kind = (HTML.Tag) o;
                        if (kind == HTML.Tag.IMG) {
                            // HERE is the call to the special class...
                            return new BASE64ImageView(elem);
                        }
                    }
                    return super.create(elem);
                }
            };
        }
        return factory;
    }

}

With that done, implement the special class, based on the openjdk code. 完成后,基于openjdk代码实现特殊类。 Download the source code here , and open the file openjdk/jdk/src/share/classes/javax/swing/text/html/ImageView.java. 这里下载源代码,然后打开文件openjdk / jdk / src / share / classes / javax / swing / text / html / ImageView.java。 Because it have almost everything private, I found it easier to copy it entirely and then change the method needed to load BASE64 Images: 因为它几乎所有都是私有的,我发现更容易完全复制它然后更改加载BASE64图像所需的方法:

private void loadImage() {
    String b64 = getBASE64Image();
    BufferedImage newImage = null;
    try (ByteArrayInputStream bais = new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(b64))) {
        newImage = ImageIO.read(bais);
    } catch (IOException ex) {
        ...
    }
    image = newImage;
}

private String getBASE64Image() {
    String src = (String) getElement().getAttributes().getAttribute(HTML.Attribute.SRC);
    if (src == null) {
        return null;
    }
    return src.replaceFirst("data:image/png;base64,", "");
}

The getBASE64Image method just cuts the non BASE64 part of the attribute. getBASE64Image方法只删除属性的非BASE64部分。 The loadImage method is the one that has to be changed, and if it where public, would have helped cut a lot of code from the solution... loadImage方法是必须更改的方法,如果它是公共的,将有助于从解决方案中删除大量代码...

If someone has a better and preferably smaller (mine have 1000 lines of code), please share it... 如果有人有更好的,最好更小(我的代码有1000行),请分享...

By piecing these posts and some from other sources, I was able to get to get this to work for myself. 通过拼接这些帖子和其他来源的一些帖子,我能够让这个为我自己工作。

If I used the above paintComponent() function, it paints the image but any additional html items aren't display properly because they overwrite each other. 如果我使用上面的paintComponent()函数,它会绘制图像,但任何其他html项目都无法正确显示,因为它们会相互覆盖。 To fix this, I tried to minimize the methods which were modified and this approach seemed to work. 为了解决这个问题,我尝试最小化修改后的方法,这种方法似乎有效。

Anyways, this is what I did to get it working: 无论如何,这就是我为使它工作所做的事情:
1) Download the OpenJDK7 files from http://download.java.net/openjdk/jdk7/ 1)从http://download.java.net/openjdk/jdk7/下载OpenJDK7文件

2) Unzip the file and copy the openjdk/jdk/src/share/classes/javax/swing/text/html/ImageView.java to your project's src folder (Use a different name than ImageView.java. In the following example, we will call it Custom_ImageView.java). 2)解压缩文件并将openjdk / jdk / src / share / classes / javax / swing / text / html / ImageView.java复制到项目的src文件夹(使用与ImageView.java不同的名称。在下面的示例中,我们将其称为Custom_ImageView.java)。 You will have to modify this file, so open it in your text editor and do the following: 您必须修改此文件,因此请在文本编辑器中将其打开并执行以下操作:
2A) Change the class name from "ImageView" to "Custom_ImageView". 2A)将类名从“ImageView”更改为“Custom_ImageView”。
2B) Change the constructor name from "ImageView" to "Custom_ImageView". 2B)将构造函数名称从“ImageView”更改为“Custom_ImageView”。

public ImageView(Element elem) {
      ...to...
public Custom_ImageView(Element elem) {

2C) Change all "synchronized(ImageView.this)" to "synchronized(Custom_ImageView.this)" 2C)将所有“synchronized(ImageView.this)”更改为“synchronized(Custom_ImageView.this)”
2D) Change the loadImage() to this: 2D)将loadImage()更改为:

// Modified method in Custom_ImageView.java
private void loadImage() {
    URL src = getImageURL();        
    Image newImage = null;
    if (src != null) {
        Dictionary cache = (Dictionary)getDocument().
                                getProperty(IMAGE_CACHE_PROPERTY);
        if (cache != null) {
            newImage = (Image)cache.get(src);                
        }
        else {                 
            newImage = Toolkit.getDefaultToolkit().createImage(src);                                
            if (newImage != null && getLoadsSynchronously()) {
                // Force the image to be loaded by using an ImageIcon.
                ImageIcon ii = new ImageIcon();
                ii.setImage(newImage);
            }
        }
    } else {  // BEGIN:  Modified code...              
        //System.out.println("[DEBUG] Image Source:  " + src);
        System.out.println("[DEBUG] loadImage() - newImage = null");
        String b64 = getBASE64Image();        
        BufferedImage newBufferedImage = null;
        try (ByteArrayInputStream bais = new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(b64))) {
            newBufferedImage = ImageIO.read(bais);
        } catch (IOException ex) {
            System.out.println("[ERROR] in loadImage() \n\t" + ex);
        } // End catch()...
        newImage = newBufferedImage;
    }  // FINISH: Modified code...
    image = newImage;
} 

Compared the above snippet to the original code: 将上面的代码段与原始代码进行比较:

// Original code from ImageView.java (OpenJDK7)...
private void loadImage() {
    URL src = getImageURL();
    Image newImage = null;
    if (src != null) {
        Dictionary cache = (Dictionary)getDocument().
                                getProperty(IMAGE_CACHE_PROPERTY);
        if (cache != null) {
            newImage = (Image)cache.get(src);
        }
        else {
            newImage = Toolkit.getDefaultToolkit().createImage(src);
            if (newImage != null && getLoadsSynchronously()) {
                // Force the image to be loaded by using an ImageIcon.
                ImageIcon ii = new ImageIcon();
                ii.setImage(newImage);
            }
        }
    }
    image = newImage;
}

2E) Within the Custom_ImageView class, add a method called getBASE64Image(), this is used by the modified loadImage() method: 2E)在Custom_ImageView类中,添加一个名为getBASE64Image()的方法,修改后的loadImage()方法使用它:

// Add this method to Custom_ImageView.java
private String getBASE64Image() {
    String src = (String) getElement().getAttributes().getAttribute(HTML.Attribute.SRC);
    if (src == null) {
        return null;
    }
    // This doesn't account for "data:image/png;charset=utf-8;base64,"
    //return src.replaceFirst("data:image/png;base64,", "");
    // So I delete all data from beginning of line (^) to ";base64"
    return src.replaceFirst("^.*;base64,", "");
} // End getBASE64Image()...

This finishes up the Custom_ViewImage.java modifications. 这样就完成了Custom_ViewImage.java修改。

3) Now we need to create our own HTMLEditorKit which will be used by our JEditorPane. 3)现在我们需要创建自己的HTMLEditorKit,它将由我们的JEditorPane使用。 We will call this file Custom_HTMLEditorKit.java. 我们将此文件称为Custom_HTMLEditorKit.java。 As stated above, we will need to override the getViewFactory() method so that it knows to use our modified file (Custom_ImageView). 如上所述,我们需要覆盖getViewFactory()方法,以便它知道使用我们修改过的文件(Custom_ImageView)。 The code should look like this: 代码应该如下所示:

package my.app.package;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;

public class Custom_HTMLEditorKit extends HTMLEditorKit {
    private static HTMLFactory factory = null;    

    @Override
    public ViewFactory getViewFactory() {
        if (factory == null) {
            factory = new HTMLFactory() {

                @Override
                public View create(Element elem) {
                    AttributeSet attrs = elem.getAttributes();
                    Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
                    Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
                    if (o instanceof HTML.Tag) {
                        HTML.Tag kind = (HTML.Tag) o;
                        if (kind == HTML.Tag.IMG) {
                            // HERE is the call to the special class...
                            return new Custom_ImageView(elem);
                        } // End if(kind == IMG)...
                    } // End if(instance of Tag)...
                    return super.create(elem);
                } // End create()...                
            }; // End new HTMLFactory()...
        } // End if(factory == null)...
        return factory;
    } // End getViewFactory()... 
} // End Custom_HTMLEditorKit()...


4) Now we are ready to use the code. 4)现在我们准备使用代码了。 Within our main app, you just have to reassign the HTMLEditorKit to Custom_HTMLEditorKit. 在我们的主应用程序中,您只需将HTMLEditorKit重新分配给Custom_HTMLEditorKit即可。

package my.app.package;

import javax.swing.JEditorPane;
import javax.swing.JFrame;


public class ExpInlineImage {

    public ExpInlineImage() {
        JFrame jf = new JFrame();
        JEditorPane jep = new JEditorPane();
        jep.setEditable(false);
        jep.setContentType("text/html");
        jep.setEditorKit(new Custom_HTMLEditorKit());
        jep.setText(
                  "<html><body>test<br><img src=\"data:image/png;base64,iVBORw"
                + "0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuklEQVR42sWVv09TU"
                + "RTH71AXE40xaRdXEwbD4ODiIFr6EwcXN1cnR0cXoyTWUjcH3n0dDG4aEwca"
                + "oqggWn8ERWtoKdVBoA2lxbj4BxzPt+eaPEn7nhDeleRDTr733vP9vtvXU0V"
                + "E6n8i//79L5KdUKVUXtHobUXxnIAaGtawZzcNdxvgEIw2u0Va39SMa9A9DW"
                + "vYE2aAKJ4WhpPTipySgBoa1rAnzAAxXPnalkvOjCL3iYAaGtawJ/QA650ia"
                + "TYuPhVQQ7MbAOazAmqrAdb4hXOe8fW/EFBDsxdgmwM8Z/M5ATU0ewF+cACY"
                + "vxRQQ7MS4NwtRd9xA/NsviCghoa1vQaIjP2ZcLnBxNng9E1F37p3yYH5awE"
                + "1NKxhj18PeIx5J6YJ0Jtwaz/vUKObZwoDqW8VaL5xkfQr/gaUBdTQsOZ3Fr"
                + "3h8dfENAGiCU5X79ygSW7oBKDf8JO/ZXMDamhB59AbHomcZ2KaADGIy+1r5"
                + "HAjjabv+lMMYNA59ERveCS8L6s3QKV9lRxsfs+H9hn0RG94DAyw1L5CepEP"
                + "fOCn8cFd7I/vGQa94dE3QJpfjMr2ZZr6oui+D/c+c8OP0tALNKz5nUVveKT"
                + "zfQKcL6hfmQnVWxxEin9uR8YVlVuXSC+xsQE1NKxhj18PeCSuq/LOl/AIc5"
                + "KJM6M+ZHB9C60LpD+xuQE1NHO1mYAeI8yJnV/DAyZELIChJJvMtVKkK2xuQ"
                + "A0tKQGGAnpEjXlkT6M4xSazzTPk8ufpBVrKxm8BTGaap8itsvGyoSqatQCl"
                + "jWFya2LcoyaatQCPW8dJr/BnXzOsiGYtwKPmMdKrbFw3rIpmJUCaTR60jlL"
                + "xK199Q0ANLW0jQJaHycPOQZpq8mTbMDRFy+bDDxDNjKsqjPC0XqAlvRMupA"
                + "CHmWHmbOCECylAxISIBk64kALs+99vkl5XSNCDZhMAAAAASUVORK5CYII="
                + "\"></body></html>");

        jf.getRootPane().setContentPane(jep);
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
    } // End constructor...

    public static void main(String[] args) {
        ExpInlineImage app = new ExpInlineImage();
    } // End main()...

} // End ExpInlineImage{}...


5) Run the app and you should see an image of a green plus sign below the word "test". 5)运行应用程序,您应该在“test”一词下面看到绿色加号的图像。


Some things worth mentioning: 值得一提的是:
* border="1" seems to work fine. * border =“1”似乎工作正常。 This will draw a border around the image. 这将在图像周围绘制边框。
* width="100px" works fine. * width =“100px”工作正常。 Sets width to 100 pixels regardless of original size. 无论原始大小如何,都将宽度设置为100像素。
* width="100" works fine. * width =“100”工作正常。 Sets width to 100 pixels regardless of original size. 无论原始大小如何,都将宽度设置为100像素。
* width="50%" doesn't work. * width =“50%”不起作用。 This sets the width to 50 pixels instead of 50 percent of original size. 这会将宽度设置为50像素,而不是原始大小的50%。

For those interested, I posted the example on github. 对于那些感兴趣的人,我在github上发布了这个例子。
https://github.com/GreenElm/expInlineImage/tree/master/src/expinlineimage https://github.com/GreenElm/expInlineImage/tree/master/src/expinlineimage

Or download repo by... 或者通过...下载回购

git remote add origin https://github.com/GreenElm/expInlineImage.git


I hope this help. 我希望这有帮助。

An other way to hook an ImageView: 另一种挂钩ImageView的方法:

public class Base64ImageView extends ImageView {

private URL url;

public Base64ImageView(Element elem) {
    super(elem);

    populateImage();
}

private void populateImage() {
    Dictionary<URL, Image> cache = (Dictionary<URL, Image>) getDocument()
            .getProperty("imageCache");
    if (cache == null) {
        cache = new Hashtable<URL, Image>();
        getDocument().putProperty("imageCache", cache);
    }

    URL src = getImageURL();
    cache.put(src, loadImage());

}

private Image loadImage() {
    String b64 = getBASE64Image();
    BufferedImage newImage = null;
    ByteArrayInputStream bais = null;
    try {
        bais = new ByteArrayInputStream(
                Base64.decodeBase64(b64.getBytes()));
        newImage = ImageIO.read(bais);
    } catch (Throwable ex) {
    }
    return newImage;
}

private String getBASE64Image() {
    String src = (String) getElement().getAttributes()
            .getAttribute(HTML.Attribute.SRC);
    if (!isBase64Encoded(src)) {
        return null;
    }
    return src.substring(src.indexOf("base64,") + 7, src.length() - 1);
}

@Override
public URL getImageURL() {
    String src = (String) getElement().getAttributes()
            .getAttribute(HTML.Attribute.SRC);
    if (isBase64Encoded(src)) {

        this.url = Base64ImageView.class.getProtectionDomain()
                .getCodeSource().getLocation();

        return this.url;
    }
    return super.getImageURL();
}

private boolean isBase64Encoded(String src) {
    return src != null && src.contains("base64,");
}

}

you can NOT display an BufferedImage within an HTML-Tag! 你不能在HTML标签中显示BufferedImage!

you can create an BufferedImage without saving it, you can create a BufferedImage from an URI/URL and you can display HTML within most swing components, but you can't do both in one; 你可以在不保存的情况下创建BufferedImage,你可以从URI / URL创建一个BufferedImage,你可以在大多数swing组件中显示HTML,但你不能同时在一个组件中显示;

maybe you can customize your swing component by overriding the paintComponent method and paint html and then image (maybe you want to to it the other way round). 也许你可以通过覆盖paintComponent方法并绘制html然后图像来自定义你的swing组件(也许你想要反过来)。

private BufferedImage buffImg; //won't be stored

private void readImage(){
    URL url = URI.create("myUri").toURL();
    buffImg = ImageIO.read(url);
}


@Override
public void paintComponent (Graphics gr){
    super(gr); //draws your html code        
    gr.drawImage(buffImg, x, y, obs); //draweing the buffImage
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM