I'm using a JEditorPane as a "rubber stamp" to render HTML text to a PDF. I need the text to wrap at specific widths, and need to apply a white "highlight" behind the text. As such, I'm creating a JEditorPane in the PDF rendering thread, setting the text and stylesheet, and then painting it to the PDF graphics. However, I'm getting an intermittent NullPointerException deep in the bowels of the HTML Editor Kit. This is reproducible with this SSCCE:
import javax.swing.*;
import javax.swing.text.View;
import javax.swing.text.html.HTMLDocument;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author sbarnum
*/
public class TextMarkerUtilsTest {
public static void main(String[] args) throws Exception {
Rectangle bounds = new Rectangle(255, 255);
BufferedImage image = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB);
Graphics2D d = image.createGraphics();
d.setClip(bounds);
for (int i=0; i<1000; i++) {
JEditorPane renderHelper = new JEditorPane("text/html", "<html><body>This is my text.</body></html>");
HTMLDocument document = (HTMLDocument) renderHelper.getDocument();
document.getStyleSheet().addRule("foo{color:black;}");
View rootView = renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d, bounds);
}
}
}
Running the above throws the following exception, usually after just a few times through the loop:
java.lang.NullPointerException
at sun.font.FontDesignMetrics$MetricsKey.init(FontDesignMetrics.java:199)
at sun.font.FontDesignMetrics.getMetrics(FontDesignMetrics.java:267)
at sun.swing.SwingUtilities2.getFontMetrics(SwingUtilities2.java:949)
at javax.swing.JComponent.getFontMetrics(JComponent.java:1599)
at javax.swing.text.LabelView.getFontMetrics(LabelView.java:154)
at javax.swing.text.html.InlineView.calculateLongestWordSpanUseWhitespace(InlineView.java:246)
at javax.swing.text.html.InlineView.calculateLongestWordSpan(InlineView.java:191)
at javax.swing.text.html.InlineView.getLongestWordSpan(InlineView.java:177)
at javax.swing.text.html.ParagraphView.calculateMinorAxisRequirements(ParagraphView.java:140)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:551)
at javax.swing.text.html.ParagraphView.getMinimumSpan(ParagraphView.java:261)
at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:886)
at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:129)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.getMinimumSpan(BoxView.java:551)
at javax.swing.text.html.BlockView.getMinimumSpan(BlockView.java:361)
at javax.swing.text.BoxView.calculateMinorAxisRequirements(BoxView.java:886)
at javax.swing.text.html.BlockView.calculateMinorAxisRequirements(BlockView.java:129)
at javax.swing.text.BoxView.checkRequests(BoxView.java:918)
at javax.swing.text.BoxView.setSpanOnAxis(BoxView.java:326)
at javax.swing.text.BoxView.layout(BoxView.java:691)
at javax.swing.text.BoxView.setSize(BoxView.java:380)
at javax.swing.plaf.basic.BasicTextUI$RootView.setSize(BasicTextUI.java:1703)
at javax.swing.plaf.basic.BasicTextUI$RootView.paint(BasicTextUI.java:1422)
at com.prosc.msi.model.editor.TextMarkerUtilsTest$1.run(TextMarkerUtilsTest.java:40)
at java.lang.Thread.run(Thread.java:680)
Some interesting findings:
If the above runs in the Event Dispatch Thread, it works
If I take out the call to addRule("foo{color:black;}"), it works (I need to specify rules, but it doesn't seem to matter what the rule is, it fails if any rules are added)
The problem is in javax.swing.text.GlyphPainter1.sync(), where javax.swing.text.GlyphView.getFont() is returning null. By setting a conditional breakpoint, I see that the GlyphView in this case is a javax.swing.text.html.InlineView. Calling getFont() after the breakpoint has stopped returns a non-null font, so something is not being initialized in time.
I realize that the swing components are not thread-safe, but shouldn't I be able to instantiate a JEditorPane in a background thread and manipulate it safely in that background thread, as long as only the one thread is making calls to the component?
As you are using only lightweight components, headless mode may be an option. You can keep the work out of your GUI's EDT using ProcessBuilder, illustrated here.
public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Rectangle bounds = new Rectangle(255, 255);
BufferedImage image = new BufferedImage(
bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB);
Graphics2D d = image.createGraphics();
d.setClip(bounds);
for (int i = 0; i < 1000; i++) {
JEditorPane renderHelper = new JEditorPane(
"text/html", "<html><body>This is my text.</body></html>");
HTMLDocument document = (HTMLDocument) renderHelper.getDocument();
document.getStyleSheet().addRule("foo{color:black;}");
View rootView = renderHelper.getUI().getRootView(renderHelper);
rootView.paint(d, bounds);
}
}
});
}
Thanks to Marko for the suggestion to look for callbacks to the Event Dispatch Thread, I ended up finding one in HTMLDocument.styleChanged(). My subclass:
public class ThreadFriendlyHTMLDocument extends HTMLDocument {
#Override
protected void styleChanged(final Style style) {
// to fix GlyphPainter1.sync NullPointerException, we call this in the current thread, instead of the EDT
DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
this.getLength(),
DocumentEvent.EventType.CHANGE);
dde.end();
fireChangedUpdate(dde);
}
}
Related
I am working on a Java program that takes in a large amount of files (3000 max) with an associated array of 1/0's. Currently I have a visualization of the array where there is a grid where each box is filled black for 1 or white for 0. When drawn it runs well but takes around a minute to fully load (and potentially locks the computer up in the meantime.) Is there a way I can: 1, not display the window till it is done
(i.e JFrame create,
//draw window
frame.setVisible(true))
and 2, track the progress of the process so that I can use a progress bar with it?
edit: Can I run a thread to draw it and then simply make a while loop to only display it once the thread is completed?
In the example below, a SwingWorker sets pixels in a BufferedImage based on the data read from a random file. Note that Thread.sleep() is used to simulate latency; it is otherwise not required. You can add a JProgressBar as shown here.
Is there a better way to get simple colored boxes?
Yes. In the example below, each pixel represents one cell. For larger boxes, return a multiple of the image size, e.g.
#Override
public Dimension getPreferredSize() {
return new Dimension(2 * N, 2 * N);
}
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
/**
* #see https://stackoverflow.com/a/25043676/230513
*/
public class WorkerTest {
private static final int N = 256;
private final BooleanPanel panel = new BooleanPanel();
private class BooleanPanel extends JPanel {
private BufferedImage image;
public void setImage(BufferedImage bi) {
this.image = bi;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(N, N);
}
}
private class BufferedImageWorker extends SwingWorker<BufferedImage, BufferedImage> {
#Override
protected BufferedImage doInBackground() throws Exception {
BufferedImage image = new BufferedImage(N, N, BufferedImage.TYPE_INT_ARGB);
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("/dev/random")))) {
for (int row = 0; row < N; row++) {
for (int col = 0; col < N; col++) {
image.setRGB(col, row, dis.readByte() < 0 ? 0xffffffff : 0xff000000);
}
Thread.sleep(40); // ~25 Hz
publish(image);
}
return image;
}
}
#Override
protected void process(List<BufferedImage> list) {
for (BufferedImage bi : list) {
panel.setImage(bi);
panel.repaint();
}
}
}
private void display() {
JFrame f = new JFrame("WorkerTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
new BufferedImageWorker().execute();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new WorkerTest().display();
});
}
}
I would definitely use a SwingWorker in this case. Basically, maybe something along these lines (I'm not sure what type of object your 'visualization' is, so for simplicity, I'll just say it's an Image). You can add this at the bottom of your class. You'll obviously have to edit it to make it work for you.
protected class DrawGridTask extends SwingWorker<Image, Object> {
ObjectToPutImageOn imageObject;
public DrawGridTask(ObjectToPutImageOn obj) {
this.imageObject = obj;
}
protected Image doInBackground() {
// generate your Image or graphic or whatever here
return Image;
}
protected void done() {
imageObject.drawThisCompletedImage(get());
}
}
To call this method, you would run (new DrawGridTask(objectToPutImageOn)).execute();
All the code in doInBackground() will run on it's own worker thread. Done() runs on the event dispatch thread, and gets the reference doInBackground() returns when it calls get().
There is more information here, including how to do progress updates at: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
Since I mentioned Images, if you do work with them, you might also want to take a look at the MediaTracker class, this can be very useful for blocking until an image is ready.
My problem is that when I run my program I get a white screen and text from an earlier build instead of the background image that's suppose to be displayed. I've deleted all the code that was associated with that build.
I've looked around for help and all the threads I've seen say to write the code how I've set it up. I don't understand where the displayed background is even coming from.
Here is the relivent code:
package tactics;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
public class Tactics2 extends JFrame{
private Screen s;
private BufferedImage bg;
private BufferedImage template;
private boolean loaded = false;
public static void main(String[] args) throws IOException{
DisplayMode dm = new DisplayMode(1024, 768, 16, DisplayMode.REFRESH_RATE_UNKNOWN);
Tactics2 t = new Tactics2();
t.run(dm);
}
//run method
public void run(DisplayMode dm) throws IOException{
loadpics();
s = new Screen();
try{
s.setFullScreen(dm, this);
try{
Thread.sleep(5000);
}catch(InterruptedException ex){}
}finally{
s.restoreScreen();
}
}
public void loadpics() throws IOException{
bg = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);
template = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);
ChaosBack cb = new ChaosBack();
bg = cb.ChaosBack(bg, template);
loaded = true;
repaint();
}
#Override
public void paint(Graphics g){
if(loaded){
g.drawImage(bg, 0, 0, null);
}
}
}
You've broken the paint chain
#Override
public void paint(Graphics g){
if(loaded){
g.drawImage(bg, 0, 0, null);
}
}
Basically, you've failed to call super.paint. Graphics is a shared resource, that is, everything painted for a given paint cycle uses the same Graphics context.
Part of the job of the paint chain is to prepare it for painting by clearing the Graphics context.
You should avoid overriding paint of a top level container for a number reasons. It's not double buffered, so it may flicker as it's updated and it doesn't take into consideration the frame decorations, meaning you can end up painting underneath the borders of the frame, instead within the viewable area.
You'd better of creating a custom component, extending from something like JPanel and overriding it's paintComponent method (making sure you call super.paintComponent)
Thread.sleep(5000); is a REALLY bad idea within a Swing application. It's possible to actually stop your application cold and stop it from been updated/painted or respond to any user interaction.
Swing is not thread safe. This means that all changes to the UI must be made from within the context of the Event Dispatching Thread.
Take a look at:
Performing Custom Painting
Painting in AWT and Swing
Concurrency in Swing
Initial Threads
How to Use Swing Timers
For details and ideas
I have tried to look at other topics with similar question like mine, and most of those solutions appear to point to fixing the classpath for images... so, I tried those by changing the classpath to absolute and using class get resource, but it still won't render the images. I have a suspicion that it has to do with the main method. I don't completely understand how that method works since I copied the source code somewhere online. I am using the Eclipse editor, and I already had put the image files alongside the Flap class file.
package wing;
import java.awt.*;
import javax.swing.*;
public class Flap extends JComponent implements Runnable {
Image[] images = new Image[2];
int frame = 0;
public void paint(Graphics g) {
Image image = images[frame];
if (image != null) {
// Draw the current image
int x = 0;
int y = 0;
g.drawImage(image, x, y, this);
}
}
public void run() {
// Load the array of images
images[0] = new ImageIcon(this.getClass().getResource("/Wing/src/wing/wing1.png"));
images[1] = new ImageIcon(this.getClass().getResource("/Wing/src/wing/wing2.png"));
// Display each image for 1 second
int delay = 10000; // 1 second
try {
while (true) {
// Move to the next image
frame = (frame+1)%images.length;
// Causes the paint() method to be called
repaint();
// Wait
Thread.sleep(delay);
}
} catch (Exception e) {
}
}
public static void main(String[] args) {
Flap app = new Flap();
// Display the animation in a frame
JFrame frame = new JFrame();
frame.getContentPane().add(app);
frame.setSize(800, 700);
frame.setVisible(true);
(new Thread(app)).start();
}
}
ImageIcon is not an Image :
images[0] = new ImageIcon(this.getClass().getResource("/Wing/src/wing/wing1.png")).getImage();
The application never ends, in main :
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
if isn't there any another JComponent(s) added to the public class Flap extends JComponent implements Runnable {
put Image as Icon to the JLabel
use Swing Timer instead of Runnable#Thread (required basic knowledge about Java and Threads too)
if there is/are another JComponent(s) added to the public class Flap extends JComponent implements Runnable {
don't use paint() use paintComponent() for Swing JComponents
use Swing Timer instead of Runnable#Thread (required basic knowledge about Java and Threads too)
in both cases load image as local variable, don't reload images forever
in both cases you have invoke Swing GUI from InitialThread
The resource name "/Wing/src/wing/wing1.png" looks suspicious: it means to locate a resource in the "/Wing/src/wing/" directory, which is most likely not where the resource actually is. Try "/wing/wing1.png" (similarly for the others)
The reason is that the src folder contains the source, which will be converted to classes. So "src/wing/Flap.java" will have the class path "/wing/Flap.class"; similarly for resources (depending on how you are packaging them).
Also, make sure the resource is indeed where you expect it to be (e.g. next to the Flap.class file in the output directory), otherwise the class loader will not find it.
I have been told and have read that SWT Objects must be explicitly disposed by calling their dispose method. However, in my own testing with the following code, I have noted that at least Shells report themselves as disposed even though the dispose method is never called (nor appears) anywhere in my code.
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Test {
private static int numDisposals = 0;
private static List<Shell> shells = new ArrayList<Shell>();
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 3; i++) {
Shell s = new Shell(d);
shells.add(s);
s.setText(String.valueOf(i));
s.open();
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent notUsed) {
numDisposals++;
printShellStatus();
}
});
}
while (numDisposals < 3) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
printShellStatus();
}
public static void printShellStatus() {
System.out.println("Which shells are disposed?");
for (Shell shell : shells) {
if (shell.isDisposed()) {
System.out.println("I am disposed.");
} else if (!shell.isDisposed()) {
System.out.println("I am NOT disposed.");
}
}
}
}
So does Shell really need to be explicitly disposed? If so, how do you know when to dispose a Shell, and where should the dispose method appear?
The paper that you cite makes this clear:
Widgets themselves do not usually need to be disposed
programmatically. A shell and its children are disposed when the user
closes its window.
So while a shell does need to be disposed, the burden is not on you to do so. Nor do you need to call dispose on any of the children, as disposing a parent will do that for you. Again, from the link you cite:
When you dispose a Shell, its children are disposed. In fact,
disposing any Composite will dispose all of the Composite's children.
However, you do have to make sure that you dispose resources you create that are not children. For example: colors and fonts. You do explicitly need to call their dispose method. It's best to hook a dispose listener to the Composite you're using them in to do this. For example:
public class MyComposite extends Composite
{
private final Color color;
public MyComposite(Composite parent, int style)
{
super(parent, style);
color = new Color(getShell().getDisplay(), 255, 255, 255);
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e)
{
color.dispose();
}
});
}
}
It's important to note, however, that you should not dispose Colors that you use but do not create. For example, do not dispose the system colors available from Display#getSystemColor().
OK so here's my code: http://www.so.pastebin.com/Qca4ERmy
I am trying to use buffers so the applet won't flicker upon redraw() but it seems I am having trouble. The applet still flickers....
Help?
Thank you.
I made a quick video about this problem: http://www.vimeo.com/12035196
Create a Swing applet. Swing is double buffered by default so you should not have this problem. Start with the section from the Swing tutorial on How to Make Applets for the proper way to create a Swing applet.
The best way I've done it is to create another image the same size as your applet, draw to that, then in your paint / update method copy the contents of that image to your graphics object. You have to make sure that you aren't updating the other image when you draw to your applet otherwise it will cause flicker. Drawing should probably be done in another Thread as well, just to make things a little easier to understand.
I don't have access to my code so the following might be a little off (and the code may not be the most efficient):
public class MyApplet extends Applet {
Image offscreen;
boolean pageFlipped = false;
Thread drawingThread;
public void init() {
offscreen = createImage(this.getWidth(), this.getHeight());
drawingThread = new Thread(new DrawingLoop());
drawingThread.start();
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
if (!pageFlipped) {
g.drawImage(offscreen, 0, 0);
pageFlipped = true;
}
}
class DrawingLoop implements Runnable {
public void run() {
while (true) {
Graphics g = offscreen.getGraphics();
if (pageFlipped) {
// do your graphics code here
pageFlipped = false;
}
}
}
}
}
Hope this helps!
-Dan
You can try to solve this issue using a BufferedImage, in this way you just create a BufferedImage that is compatible with your frame and then draw everything there before blitting the whole image onto the JFrame's content.
A better approach is to use automatic buffering with BufferStrategy class, you can read a tutorial about it here.