Display base64-encoded image - java

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.

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.

Related

How to make Java URI class to stop using a file

I have a small Java desktop application, it reads some image files and displays them. The problem is when I want to execute something (let me say, an exiftool operation) on these files, it denies because Java is still using them.
edit: This happens only on Windows, you can't write on an animated GIF file (converted to a URL object) which is being processed (being displayed) by Java, but on Ubuntu you can edit that file Metadata, system does not think the file is being processed.
Here is that part of my code.
I read the file;
ImageInputStream iis = null;
ImageReader reader = null;
iis = ImageIO.createImageInputStream(
f);
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(iis);
reader = (ImageReader) imageReaders.next();
Path pathe = f.toPath();
String mimeType = Files.probeContentType(pathe);
ImageIcon icon = null;
Here is the part I process images, resize them and make JLabel to view them.
The reason why I am not just using Java ImageIO to fill that label with an image, ImageIO can only display the first frame of an animated GIF file. Converting an image to URL this way keep the image animated (even after resized).
ImageIcon icon = null;
Integer labelWidth = this.imageLabel.getWidth();
Integer labelHeight = this.imageLabel.getHeight();
URI img;
img = f.toURI();
URL umg = img.toURL();
icon = new ImageIcon(umg);
//some calculations for setting labelWidth and labelHeight here
icon.setImage(icon.getImage().getScaledInstance(labelWidth, labelHeight,
Image.SCALE_DEFAULT));
At the end, streams are closed.
this.imageLabel.setIcon(icon);
iis.close();
reader.dispose();
And then I try to execute some exiftool commands via a process, it succesfully reads the image Metadata. But when updating the data, it says "Error renaming temporary file to C:/Users/path..." if the image is an animated GIF.
No problems occur if the image is a non animated image, JPEG, PNG or GIF, it can read and update the Metadata, I guess .
When I cancel the image displaying part of the code, I can write on the image Metadata without errors. If I read a JPEG file and display it, still no problems. If I read an animated GIF and display it (animated, does it keep the file connection open?) no modifications on this file can be done, not in my program nor on cmd.exe while the debugging session is not closed. After I quit debugging, exiftool process on cmd.exe starts working normally.
Closing ImageInputStream or ImageReader did not help.
Is there a way to make Java process (if the file is animated, I use URI, URL classes) release the file after read operation? Do these classes I mentioned have methods for releasing, closing, shutting down, kill process etc. I need to read the animated images and display them animated and make update operations on them.
thanks for the comments, here is the problem and the solution.
First of all I removed the unnecessary parts from the code. ImageInputStream and ImageReader were used to check image validation and detect image format (had to use different operations to GIF files) which I do not need anymore.
I still need to use File->URI->URL convertion to display animated GIFs. This is my old code.
URI img = f.toURI();
URL umg = img.toURL();
icon = new ImageIcon(umg);
This code kept connection to file open and blocked other processes editing the image file. (Only animated GIF files, on Windows system)
Here is the new code:
//these 2 lines are same
URI img = f.toURI();
URL umg = img.toURL();
InputStream is = umg.openStream();
byte[] byteChunk = new byte[4096];
int n;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((n = is.read(byteChunk)) > 0)
{
baos.write(byteChunk, 0, n);
}
byte[] dd= baos.toByteArray();
icon = new ImageIcon(dd);
is.close();
With this approach URI(image) is read via stream and ImageIcon is created from image file's byte array not directly from URL, and after that operation InputStream is closed, so the block on that file is released. (If "is.close()" line is not executed, file is still being processed and blocked for other write operations)
Have you tried to load the image with Toolkit.createImage method?
Give this a go:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
private static class DrawPanel extends JPanel {
private Image i = null;
public void loadImage(final File f) {
//Most important point: load image via Toolkit.
//I think it must support GIF, JPEG and PNG.
i = getToolkit().createImage(f.getAbsolutePath());
repaint();
}
#Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (i != null) {
//g.drawImage(i, 0, 0, this); //Draw full scale image.
g.drawImage(i, 0, 0, getWidth(), getHeight(), this); //Draw scaled/streched image.
//Supplying 'this' in place of ImageObserver argument to the drawImage method is also very important!
}
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final DrawPanel imagePanel = new DrawPanel();
imagePanel.setPreferredSize(new Dimension(500, 350));
final JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(true);
final JButton load = new JButton("Load");
load.addActionListener(e -> {
if (fileChooser.showOpenDialog(imagePanel) == JFileChooser.APPROVE_OPTION)
imagePanel.loadImage(fileChooser.getSelectedFile());
});
final JPanel contents = new JPanel(new BorderLayout());
contents.add(imagePanel, BorderLayout.CENTER);
contents.add(load, BorderLayout.PAGE_END);
final JFrame frame = new JFrame("Images");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
There are several createImage methods to choose from.
I think they support animated GIFs, and non-animated JPGs and PNGs. I tested only for a GIF, but I have also worked with this in the past for JPGs and PNGs.
I think the animated image is completely loaded into memory (all frames). So you shouldn't have problem modifying the image after reading it.
Get the Toolkit related to the Component which will display the Image. I think also the Toolkit.getDefaultToolkit should do the job under certain circumstances. Then use the drawImage method of the Graphics object to draw the image. There are several drawImage methods to choose from. You can you use for example the one that scales the image on the fly for you (by supplying new width and height along with the draw location). Important: make sure you supply the drawImage with the component which renders it as the ImageObserver (note Component implements ImageObserver).

Java-Selenium : TakesScreenshot size issue

I have created 'takescreenshot' reusable method to capture screenshot and have been calling wherever I need it.
however I am facing one strange issue here. Every time this function is called the captured image size keeps increasing
e.g 252K -> 278K -> 310K -> 400K ...
These captured images I am using in ExtentReport. Also apart from selenium session image, I do see a black background image being captured not sure where it is coming from.
method code is as below:
public static void takescreenshot(ExtentTest Test,String Status){
Date d=new Date();
String CurrentTimeStamp=d.toString().replace(":", "_").replace(" ", "_");
File scrFile =((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(CurrentTimeStamp+".png"));
if(Status.equals("pass")){
Test.log(LogStatus.PASS, "snapshot below:-"+CurrentTimeStamp+".png"));
}else if(Status.equals("fail")){
Test.log(LogStatus.FAIL, "snapshot below:-"+CurrentTimeStamp+".png"));
}
}
if I hardcode some existing image in extentreport code then everything works fine.
Has anyone come across this issue ever.
I had written a code to capture screenshot of elements which works fine. May be this will help you. I don't know what Extendreport is, so can't help you there.
public static void takeElementScreenshot(WebDriver driver, WebElement element){
try{
// Get entire page screenshot
File screenshot = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
BufferedImage fullImg = ImageIO.read(screenshot);
// Get the location of element on the page
Point point = element.getLocation();
// Get width and height of the element
int eleWidth = element.getSize().getWidth();
int eleHeight = element.getSize().getHeight();
// Crop the entire page screenshot to get only element screenshot
BufferedImage eleScreenshot= fullImg.getSubimage(point.getX(), point.getY(),
eleWidth, eleHeight);
ImageIO.write(eleScreenshot, "png", screenshot);
// Copy the element screenshot to disk
File screenshotLocation = new File("D:\\Screenshot.png");
FileUtils.copyFile(screenshot, screenshotLocation);
}
catch(Exception e){
}
}

Render Type3 font character as image using PDFBox

In my project, I'm stuck with necessity to parse PDF file, that contains some characters rendered by Type3 fonts. So, what I need to do is to render such characters into BufferedImage for further processing.
I'm not sure if I'm looking in correct way, but I'm trying to get PDType3CharProc for such characters:
PDType3Font font = (PDType3Font)textPosition.getFont();
PDType3CharProc charProc = font.getCharProc(textPosition.getCharacterCodes()[0]);
and the input stream of this procedure contains following data:
54 0 1 -1 50 43 d1
q
49 0 0 44 1.1 -1.1 cm
BI
/W 49
/H 44
/BPC 1
/IM true
ID
<some binary data here>
EI
Q
but unfortunately I don't have any idea how can I use this data to render character into an image using PDFBox (or any other Java libraries).
Am I looking in correct direction, and what can I do with this data?
If not, are there some other tools that can solve such problem?
Unfortunately PDFBox out-of-the-box does not provide a class to render contents of arbitrary XObjects (like the type 3 font char procs), at least as far as I can see.
But it does provide a class for rendering complete PDF pages; thus, to render a given type 3 font glyph, one can simply create a page containing only that glyph and render this temporary page!
Assuming, for example, the type 3 font is defined on the first page of a PDDocument document and has name F1, all its char procs can be rendered like this:
PDPage page = document.getPage(0);
PDResources pageResources = page.getResources();
COSName f1Name = COSName.getPDFName("F1");
PDType3Font fontF1 = (PDType3Font) pageResources.getFont(f1Name);
Map<String, Integer> f1NameToCode = fontF1.getEncoding().getNameToCodeMap();
COSDictionary charProcsDictionary = fontF1.getCharProcs();
for (COSName key : charProcsDictionary.keySet())
{
COSStream stream = (COSStream) charProcsDictionary.getDictionaryObject(key);
PDType3CharProc charProc = new PDType3CharProc(fontF1, stream);
PDRectangle bbox = charProc.getGlyphBBox();
if (bbox == null)
bbox = charProc.getBBox();
Integer code = f1NameToCode.get(key.getName());
if (code != null)
{
PDDocument charDocument = new PDDocument();
PDPage charPage = new PDPage(bbox);
charDocument.addPage(charPage);
charPage.setResources(pageResources);
PDPageContentStream charContentStream = new PDPageContentStream(charDocument, charPage);
charContentStream.beginText();
charContentStream.setFont(fontF1, bbox.getHeight());
charContentStream.getOutput().write(String.format("<%2X> Tj\n", code).getBytes());
charContentStream.endText();
charContentStream.close();
File result = new File(RESULT_FOLDER, String.format("4700198773-%s-%s.png", key.getName(), code));
PDFRenderer renderer = new PDFRenderer(charDocument);
BufferedImage image = renderer.renderImageWithDPI(0, 96);
ImageIO.write(image, "PNG", result);
charDocument.close();
}
}
(RenderType3Character.java test method testRender4700198773)
Considering the textPosition variable in the OP's code, he quite likely attempts this from a text extraction use case. Thus, he'll have to either pre-generate the bitmaps as above and simply look them up by name or adapt the code to match the available information in his use case (e.g. he might not have the original page at hand, only the font object; in that case he cannot copy the resources of the original page but instead may create a new resources object and add the font object to it).
Unfortunately the OP did not provide a sample PDF. Thus I used one from another stack overflow question, 4700198773.pdf from extract text with custom font result non readble for my test. There obviously might remain issues with the OP's own files.
I stumbled upon the same issue and I was able to render Type3 font by modifying PDFRenderer and the underlying PageDrawer:
class Type3PDFRenderer extends PDFRenderer
{
private PDFont font;
public Type3PDFRenderer(PDDocument document, PDFont font)
{
super(document);
this.font = font;
}
#Override
protected PageDrawer createPageDrawer(PageDrawerParameters parameters) throws IOException
{
FontType3PageDrawer pd = new FontType3PageDrawer(parameters, this.font);
pd.setAnnotationFilter(super.getAnnotationsFilter());//as done in the super class
return pd;
}
}
class FontType3PageDrawer extends PageDrawer
{
private PDFont font;
public FontType3PageDrawer(PageDrawerParameters parameters, PDFont font) throws IOException
{
super(parameters);
this.font = font;
}
#Override
public PDGraphicsState getGraphicsState()
{
PDGraphicsState gs = super.getGraphicsState();
gs.getTextState().setFont(this.font);
return gs;
}
}
Simply use Type3PDFRenderer instead of PDFRendered. Of course if you have multiple fonts this needs some more modification to handle them.
Edit: tested with pdfbox 2.0.9

JEditorPane html document inline (embedded) image from file

I'm trying to inline (embed) an image in a JEditorPane from a file such as:
<img src="data:image/gif;utf-8,data...">
But I'm struggling with the code.
So far I have (assuming a gif file):
try
{
File imageFile = new File("C:\\test\\testImage.gif");
File htmlFile = new new File("C:\\test\\testOutput.html");
byte[] imageBytes = Files.toByteArray(imageFile);
String imageData = new String(imageBytes, "UTF-8");
String html = "<html><body><img src=\"data:image/gif;utf-8," + imageData + "\"></body></html>";
FileUtils.writeStringToFile(htmlFile, htmlText);
} catch (Exception e) {
e.printStackTrace();
}
This does create a file but the image is invalid. I'm sure I'm not converting the image the proper way...
JEditorPane (and Java HTML rendering in general) does not support Base64 encoded images.
Of course 'does not' != 'could not'.
The thing is, you'd need to create (or adjust) an EditorKit can have new elements defined. An e.g. is seen in the AppletEditorKit. You'd need to look for HTML.tag.IMG - it is is a standard image, call the super functionality, else use this source (or similar) to convert it to an image, then embed it.

Can't get img tags

I have a question. When I try to get images from a web page by using Jsoup in Java.
Here is the code:
String link = "http://truyentranhtuan.com/detective-conan/856/doc-truyen/";
Document docs = Jsoup.connect(link).timeout(60000).get();
Elements comics = docs.select("#hienthitruyen img");
System.out.println(comics.size());
for (Element comic : comics) {
int i = 0;
System.out.println(comic);
String linkImage = comic.attr("src");
if (!"".equals(linkImage)) {
URL url = new URL(linkImage);
BufferedImage image = ImageIO.read(url);
ImageIO.write(image, "jpg", new File(i + ".jpg"));
i++;
}
}
The problem is I can't get any img tag in this web page. The size of Elements always be zero.
But when I view source in this web page the img tag always be there.
If you look at the real source, not the DOM structure (for example, save the HTML page and open it in Notepad), you will see that there are no img tags there. They are all populated dynamically by the means of Javascript.
Now the problem is that Jsoup is not meant to execute Javascript, therefore you can only parse the original DOM structure, before it is modified (filled with images) by Javascript.
To do what you want, you can use HTMLUnit which can execute most of the Javascript.

Categories

Resources