I'm trying to display an animated gif inside a java application. So far it displays and animates, but where it's supposed to be either entirely black or entirely white it appears to become transparent and I can see the colour of the rectangle drawn underneath. For example, here's one of the gifs I'm using:
On a black background it's not noticeable, however when I change it to cyan:
What's really strange is that it seems like it's displaying correctly for the very first frame and then only messes up afterwards. This is the method I'm using to get and draw the image:
//Draw title
img = new ImageIcon(Display.class.getResource(titlePath)).getImage();
g.drawImage(img, 404, 430, this);
I'm overriding paintcomponent in a class that extends JPanel, and I'm only calling repaint on that class once.
What could be causing this, is it a known issue or am I making a mistake in the way I'm going about actually drawing the image? I haven't been able to find any sort of documentation about this issue.
Thanks in advance!
Here are two of the original gif files.
EDIT: Here's a sample of the code I wrote. I removed any unnecessary code to keep it short but kept everything related to the issue. It also may be worth noting that when I draw a png file (which is one of the frames used to create the gif) with the same method there's no issue. The gif was made on http://gifmaker.me/ in case it's relevant.
public class Display extends JPanel implements Constants{
private GameData GD;
private String currentImagePath;
public Display(GameData gd){
GD = gd;
this.setPreferredSize(screenDimension);
this.repaint();
}
public void draw(Location loc){
currentImagePath = loc.getCurrentImagePath();
this.repaint();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(CYAN);
g.fillRect(0, 0, screenW, screenH);
Image img;
if(OtherWorlds.isOnTitle == true){ //Title screen
//Draw image
img = new ImageIcon(Display.class.getResource(currentImagePath)).getImage();
g.drawImage(img, 10, 10, this);
//Draw other stuff
{
else{ //Regular location
//Draw image
img = new ImageIcon(Display.class.getResource(currentImagePath)).getImage();
g.drawImage(img, 10, 10, this);
//Other images and stuff in different areas
//Drawn using the exact same method as above
}
//Random other lines and stuff
}
}
}
When ever you have these types of problems, you want to start playing around with the disposalMethod of the frames.
I ran your gif through some inspection code and found the disposalMethod to be set to RESTORE_TO_BACKGROUND
So, basically, I took your gif and ran it through the following code, which created a new gif with the disposalMethod of none
So your original image is on top and the "fixed" image is on the bottom. The background is colored red to highlight the difference
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class MirrorImage {
public static void main(String[] args) {
new MirrorImage();
}
public MirrorImage() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private ImageIcon orig;
private ImageIcon mirror;
public TestPane() {
mirror(new File("Qzlxj.gif"), new File("Test.gif"));
orig = new ImageIcon("Qzlxj.gif");
mirror = new ImageIcon("Test.gif");
}
#Override
public Dimension getPreferredSize() {
return mirror == null ? new Dimension(200, 200) : new Dimension(orig.getIconWidth(), orig.getIconHeight() * 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (orig != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - orig.getIconWidth()) / 2;
int y = (getHeight() - (orig.getIconHeight() * 2)) / 2;
g2d.drawImage(orig.getImage(), x, y, this);
// AffineTransform at = new AffineTransform();
// at.setToScale(1, -1);
// at.translate(0, -mirror.getIconHeight());
// g2d.setTransform(at);
g2d.drawImage(mirror.getImage(), x, y + mirror.getIconHeight(), this);
g2d.dispose();
}
}
}
public static void mirror(File source, File dest) {
List<BufferedImage> images = new ArrayList<>(25);
List<Integer> delays = new ArrayList<>(25);
int delay = 0;
ImageOutputStream output = null;
GifSequenceWriter writer = null;
try {
String[] imageatt = new String[]{
"imageLeftPosition",
"imageTopPosition",
"imageWidth",
"imageHeight"
};
ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName("gif").next();
ImageInputStream ciis = ImageIO.createImageInputStream(source);
reader.setInput(ciis, false);
int noi = reader.getNumImages(true);
BufferedImage master = null;
for (int i = 0; i < noi; i++) {
BufferedImage image = reader.read(i);
IIOMetadata metadata = reader.getImageMetadata(i);
Node tree = metadata.getAsTree("javax_imageio_gif_image_1.0");
NodeList children = tree.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node nodeItem = children.item(j);
System.out.println(nodeItem.getNodeName());
if (nodeItem.getNodeName().equals("ImageDescriptor")) {
Map<String, Integer> imageAttr = new HashMap<String, Integer>();
NamedNodeMap attr = nodeItem.getAttributes();
// for (int index = 0; index < attr.getLength(); index++) {
// Node node = attr.item(index);
// System.out.println("----> " + node.getNodeName() + "=" + node.getNodeValue());
// }
for (int k = 0; k < imageatt.length; k++) {
Node attnode = attr.getNamedItem(imageatt[k]);
imageAttr.put(imageatt[k], Integer.valueOf(attnode.getNodeValue()));
}
if (master == null) {
master = new BufferedImage(imageAttr.get("imageWidth"), imageAttr.get("imageHeight"), BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g2d = master.createGraphics();
g2d.drawImage(image, imageAttr.get("imageLeftPosition"), imageAttr.get("imageTopPosition"), null);
g2d.dispose();
// BufferedImage frame = mirror(copyImage(master));
BufferedImage frame = copyImage(master);
ImageIO.write(frame, "png", new File("img" + i + ".png"));
images.add(frame);
} else if (nodeItem.getNodeName().equals("GraphicControlExtension")) {
NamedNodeMap attr = nodeItem.getAttributes();
Node delayNode = attr.getNamedItem("delayTime");
if (delayNode != null) {
delay = Math.max(delay, Integer.valueOf(delayNode.getNodeValue()));
delays.add(delay);
}
}
}
}
output = new FileImageOutputStream(dest);
writer = new GifSequenceWriter(output, images.get(0).getType(), delay * 10, true);
for (int i = 0; i < images.size(); i++) {
BufferedImage nextImage = images.get(i);
writer.writeToSequence(nextImage);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
writer.close();
} catch (Exception e) {
}
try {
output.close();
} catch (Exception e) {
}
}
}
public static BufferedImage mirror(BufferedImage img) {
BufferedImage mirror = createCompatibleImage(img);
Graphics2D g2d = mirror.createGraphics();
AffineTransform at = new AffineTransform();
at.setToScale(1, -1);
at.translate(0, -img.getHeight());
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, null);
g2d.dispose();
return mirror;
}
public static BufferedImage copyImage(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage newImage = createCompatibleImage(img);
Graphics graphics = newImage.createGraphics();
int x = (width - img.getWidth()) / 2;
int y = (height - img.getHeight()) / 2;
graphics.drawImage(img, x, y, img.getWidth(), img.getHeight(), null);
graphics.dispose();
return newImage;
}
public static BufferedImage createCompatibleImage(BufferedImage image) {
return getGraphicsConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
public static class GifSequenceWriter {
protected ImageWriter gifWriter;
protected ImageWriteParam imageWriteParam;
protected IIOMetadata imageMetaData;
/**
* Creates a new GifSequenceWriter
*
* #param outputStream the ImageOutputStream to be written to
* #param imageType one of the imageTypes specified in BufferedImage
* #param timeBetweenFramesMS the time between frames in miliseconds
* #param loopContinuously wether the gif should loop repeatedly
* #throws IIOException if no gif ImageWriters are found
*
* #author Elliot Kroo (elliot[at]kroo[dot]net)
*/
public GifSequenceWriter(
ImageOutputStream outputStream,
int imageType,
int timeBetweenFramesMS,
boolean loopContinuously) throws IIOException, IOException {
// my method to create a writer
gifWriter = getWriter();
imageWriteParam = gifWriter.getDefaultWriteParam();
ImageTypeSpecifier imageTypeSpecifier
= ImageTypeSpecifier.createFromBufferedImageType(imageType);
imageMetaData
= gifWriter.getDefaultImageMetadata(imageTypeSpecifier,
imageWriteParam);
String metaFormatName = imageMetaData.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);
IIOMetadataNode graphicsControlExtensionNode = getNode(
root,
"GraphicControlExtension");
//restoreToBackgroundColor
//restoreToPrevious
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute(
"transparentColorFlag",
"FALSE");
graphicsControlExtensionNode.setAttribute(
"delayTime",
Integer.toString(timeBetweenFramesMS / 10));
graphicsControlExtensionNode.setAttribute(
"transparentColorIndex",
"0");
IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
commentsNode.setAttribute("CommentExtension", "Created by MAH");
IIOMetadataNode appEntensionsNode = getNode(
root,
"ApplicationExtensions");
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
child.setAttribute("applicationID", "NETSCAPE");
child.setAttribute("authenticationCode", "2.0");
int loop = loopContinuously ? 0 : 1;
child.setUserObject(new byte[]{0x1, (byte) (loop & 0xFF), (byte) ((loop >> 8) & 0xFF)});
appEntensionsNode.appendChild(child);
imageMetaData.setFromTree(metaFormatName, root);
gifWriter.setOutput(outputStream);
gifWriter.prepareWriteSequence(null);
}
public void writeToSequence(RenderedImage img) throws IOException {
gifWriter.writeToSequence(
new IIOImage(
img,
null,
imageMetaData),
imageWriteParam);
}
/**
* Close this GifSequenceWriter object. This does not close the
* underlying stream, just finishes off the GIF.
*/
public void close() throws IOException {
gifWriter.endWriteSequence();
}
/**
* Returns the first available GIF ImageWriter using
* ImageIO.getImageWritersBySuffix("gif").
*
* #return a GIF ImageWriter object
* #throws IIOException if no GIF image writers are returned
*/
private static ImageWriter getWriter() throws IIOException {
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
if (!iter.hasNext()) {
throw new IIOException("No GIF Image Writers Exist");
} else {
return iter.next();
}
}
/**
* Returns an existing child node, or creates and returns a new child
* node (if the requested node does not exist).
*
* #param rootNode the <tt>IIOMetadataNode</tt> to search for the child
* node.
* #param nodeName the name of the child node.
*
* #return the child node, if found or a new node created with the given
* name.
*/
private static IIOMetadataNode getNode(
IIOMetadataNode rootNode,
String nodeName) {
int nNodes = rootNode.getLength();
for (int i = 0; i < nNodes; i++) {
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName)
== 0) {
return ((IIOMetadataNode) rootNode.item(i));
}
}
IIOMetadataNode node = new IIOMetadataNode(nodeName);
rootNode.appendChild(node);
return (node);
}
}
}
And finally, the "fixed" gif
The above is based on the investigations from Mirroring animated gif on load in Java - ImageIcon
Related
I'm trying to make my own, very inefficient "image copier". I'm doing it by reading the pixels from the original image on to an array and then resetting those same pixels in my default BufferedImage. And then repaint the frame. Row by row.
I'm trying to repaint the frame every after each row of pixels has been stored in the array. But the frame only gets updated once; when it finishes storing the pixels.
My code is all over the place, and I'm probably doing a lot of stuff wrong. This is for an assignment and I've been going at it for a while now and would really appreciate some help.
Here is my code:
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class pixelReloc extends JComponent {
static BufferedImage image,newImg;
static JFrame frame;
public void initialize() {
int width = getSize().width;
int height = getSize().height;
int pixels[];
int index = 0;
int j=0,i=0;
File f = new File("/path/to/file/images/shrek4life.jpg");
try{
image = ImageIO.read(f);
}catch(IOException e){}
System.out.println("checkpoint 1");
image = createResizedCopy(image,500,500,true);
newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
pixels = new int[(image.getWidth()) * (image.getHeight())];
System.out.println("checkpoint 2");
for(i= 0; i < newImg.getWidth(); i++){
for(j = 0; j < newImg.getHeight(); j++){
//get the rgb color of the old image
Color c = new Color(image.getRGB(i, j));
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
pixels[index++] = (r<<16) | (g<<8) | b;
}
newImg.setRGB(0, 0, i, j, pixels, 0, 0);
frame.getContentPane().validate();
frame.getContentPane().repaint();
}
System.out.println("checkpoint 4");
//image.setRGB(0, 0, width, height, data, 0, width);
}
public BufferedImage createResizedCopy(BufferedImage originalImage,
int scaledWidth, int scaledHeight,
boolean preserveAlpha)
{
System.out.println("resizing...");
int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
Graphics2D g = scaledBI.createGraphics();
if (preserveAlpha) {
g.setComposite(AlphaComposite.Src);
}
g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
return scaledBI;
}
public void paint(Graphics g) {
if (image == null)
initialize();
g.drawImage(newImg, 0, 0, this);
}
public static void main(String[] args) {
frame = new JFrame("P I X E L S");
frame.getContentPane().add(new pixelReloc ());
frame.setSize(500, 500);
frame.setLocation(100, 100);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
}
}
Here is the picture I'm reading the pixels from:
and this is how it's coming out:
The program doesn't give any errors or anything.
The basic answer to your question is, you're blocking the Event Dispatching Thread.
Swing is both single threaded and NOT thread safe. This means you can't run long running or blocking operations from within the EDT AND you should't not update the UI or some state the UI depends on from outside the EDT.
I recommend that you start by having a look at Concurrency in Swing.
This leaves with three basic options:
Use another Thread. This is problematic as you need to ensure that any state that the UI relies on is only updated from within the context of the EDT
Use a SwingWorker. This is basically the previous option, but with built in management which allows you to publish updates which are process'ed on the EDT
Use a Swing Timer. In your case, this is probably not the best solution, but it is the simplest.
For example
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new PixelReloc());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Pixel {
private int x, y;
private int color;
public Pixel(int x, int y, int color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getColor() {
return color;
}
}
public class PixelReloc extends JComponent {
private BufferedImage image;
private BufferedImage newImg;
public PixelReloc() {
SwingWorker<Integer[], List<Pixel>> worker = new SwingWorker<Integer[], List<Pixel>>() {
Integer pixels[];
#Override
protected Integer[] doInBackground() throws Exception {
pixels = new Integer[image.getWidth() * image.getHeight()];
int index = 0;
List<Pixel> pixies = new ArrayList<>(image.getWidth());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int color = image.getRGB(x, y);
pixels[index++] = color;
pixies.add(new Pixel(x, y, color));
}
publish(new ArrayList<Pixel>(pixies));
pixies = new ArrayList<>(image.getWidth());
Thread.sleep(100);
}
return pixels;
}
#Override
protected void process(List<List<Pixel>> chunks) {
for (List<Pixel> pixels : chunks) {
for (Pixel pixel : pixels) {
newImg.setRGB(pixel.getX(), pixel.getY(), pixel.getColor());
}
}
repaint();
}
};
File f = new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/chaotic_megatokyo_by_fredrin-d9k84so.jpg");
try {
image = ImageIO.read(f);
} catch (IOException e) {
}
System.out.println("checkpoint 1");
image = createResizedCopy(image, 200, 200, true);
newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
worker.execute();
// pixels = new int[(image.getWidth()) * (image.getHeight())];
// System.out.println("checkpoint 2");
// for (i = 0; i < newImg.getWidth(); i++) {
// for (j = 0; j < newImg.getHeight(); j++) {
// //get the rgb color of the old image
// Color c = new Color(image.getRGB(i, j));
// int r = c.getRed();
// int g = c.getGreen();
// int b = c.getBlue();
// pixels[index++] = (r << 16) | (g << 8) | b;
// }
// }
// System.out.println("checkpoint 4");
//image.setRGB(0, 0, width, height, data, 0, width);
}
#Override
public Dimension getPreferredSize() {
return image == null ? new Dimension(200, 200) : new Dimension(image.getWidth(), image.getHeight());
}
public BufferedImage createResizedCopy(BufferedImage originalImage,
int scaledWidth, int scaledHeight,
boolean preserveAlpha) {
System.out.println("resizing...");
Image scaled = originalImage.getScaledInstance(scaledWidth, -1, Image.SCALE_SMOOTH);
BufferedImage scaledBI = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = scaledBI.createGraphics();
g.drawImage(scaled, 0, 0, null);
g.dispose();
return scaledBI;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(newImg, 0, 0, this);
}
}
}
The Thread.sleep in the SwingWorker is intended to do two things:
Give time for the EDT to process the results from the process call and
Slow down the worker so that the results can be updated on the UI.
In my testing without it, the image was pretty much updated instantly.
I also recommend you take the time to better understand the paint process in Swing, start by having a look at:
Performing Custom Painting
Painting in AWT and Swing
The scaling mechanism you were using (and the one I've implemented) aren't the best solutions. I recommend having look at:
Java: maintaining aspect ratio of JPanel background image
Quality of Image after resize very low -- Java
for some better ideas.
You made a couple of mistakes, the first one is using 0 as scansize which should be the width of the image, also in the same line you should use the width and height instead of i and j.
newImg.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
There is one other mistake which I will let you find on your own, it should be obvious after you see the image with the above fixed line.
I am using PotraceJ (potrace library's java version) - note that potrace is used for vectorization of images.
Situation - potrace traces image, and stores the result in a BufferedImage, which is then displayed in a JPanel as an ImageIcon.
My problem - I want to save this BufferedImage instead as a file say vector.svg
Using ImageIO.write() I tried but no file is created when I do that. Possible reason could be Java's image writers may not write svg
I have to store this vectorized image as an svg file. How should I do that?
The code that mainly vectorzies a raster image is embeded here. While you can find the whole library here at github.
package potracej.src;
import potracej.src.compat.ConvertToJavaCurves;
import potracej.src.compat.PathElement;
import potracej.src.potracej.Bitmap;
import potracej.src.potracej.PoTraceJ;
import potracej.src.potracej.param_t;
import potracej.src.potracej.path_t;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Created with IntelliJ IDEA.
* User: san
* Date: 6/10/12
* Time: 12:55 PM
* To change this template use File | Settings | File Templates.
*/
public class Main {
static BufferedImage result;
static Bitmap bmp;
static param_t param = new param_t();
static double scale = 1;
static ImageIcon resultIcon;
static ImageIcon srcIcon;
static BufferedImage sourceImage;
static boolean renderSourceImage = false;
public static void main(String[] args) throws IOException {
Path p = Paths.get(new File(".").getCanonicalPath()+"/potracej/girl.png");
sourceImage = ImageIO.read(p.toFile());
//Toolkit.getDefaultToolkit().
WritableRaster raster = sourceImage.getRaster();
int[] iarr = new int[4];
bmp = new Bitmap((int)(sourceImage.getWidth()), (int)(sourceImage.getHeight()));
for(int y=0; y<sourceImage.getHeight(); y++) {
for(int x=0; x<sourceImage.getWidth(); x++) {
int[] pixel = raster.getPixel(x, y, iarr);
if (pixel[0] + pixel[1] + pixel[2] + pixel[3] != 0) {
bmp.put(x, y, 1);
}
}
}
BufferedImage d2 = new BufferedImage((int) (scale * sourceImage.getWidth()), (int)(scale * sourceImage.getHeight()), BufferedImage.TYPE_INT_ARGB);
Graphics2D d2g = (Graphics2D) d2.getGraphics();
d2g.scale(scale, scale);
d2g.drawImage(sourceImage, 0, 0, null);
d2g.dispose();
sourceImage.flush();
srcIcon = new ImageIcon(d2);
doTrace(scale);
JFrame frame = new JFrame("Result") {
{
setLayout(new BorderLayout());
resultIcon = new ImageIcon(result, "Result");
JButton resultButton = new JButton(resultIcon);
resultButton.setUI(new MetalButtonUI() {
#Override
protected void paintButtonPressed(Graphics g, AbstractButton b) {
//
}
});
add(resultButton, BorderLayout.CENTER);
resultButton.setPressedIcon(srcIcon);
JPanel stuff = new JPanel();
add(stuff, BorderLayout.NORTH);
stuff.setLayout(new GridLayout(4, 2));
stuff.add(new JLabel("Suppress speckles"));
final JSlider turdSlider = new JSlider(JSlider.HORIZONTAL, 0, 100, param.turdsize);
stuff.add(turdSlider);
turdSlider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
param.turdsize = turdSlider.getValue();
doRetrace();
}
});
stuff.add(new JLabel("Smooth corners"));
final JSlider smoothSlider = new JSlider(JSlider.HORIZONTAL, 0, 300, (int) (param.opttolerance * 100));
stuff.add(smoothSlider);
smoothSlider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
param.opttolerance = smoothSlider.getValue() / 100.0;
doRetrace();
}
});
stuff.add(new JLabel("Optimize paths"));
final JSlider optSlider = new JSlider(JSlider.HORIZONTAL, 0, 125, (int) (param.alphamax * 100));
stuff.add(optSlider);
optSlider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
param.alphamax = optSlider.getValue()/100.0;
doRetrace();
}
});
final JCheckBox renderSource = new JCheckBox("Render source");
stuff.add(renderSource);
renderSource.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
renderSourceImage = renderSource.getModel().isArmed();
doRetrace();
}
});
}
private void doRetrace() {
doTrace(scale);
resultIcon.setImage(result);
repaint();
}
};
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void doTrace(double scale) {
PoTraceJ poTraceJ = new PoTraceJ(param);
long l = System.currentTimeMillis();
path_t trace = null;
for(int i=0; i<10; i++) {
trace = poTraceJ.trace(bmp);
Thread.yield();
}
poTraceJ.resetTimers();
for(int i=0; i<100; i++) {
trace = poTraceJ.trace(bmp);
}
poTraceJ.printTimers();
l = System.currentTimeMillis() - l;
System.out.println("L="+l);
ArrayList<PathElement> al = new ArrayList<PathElement>();
ConvertToJavaCurves.convert(trace, new HashSet<ConvertToJavaCurves.Point>(), al);
if (result != null)
result.flush();
result = new BufferedImage((int)(scale * bmp.getWidth()), (int)(scale * bmp.getHeight()), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D)result.getGraphics();
g2.scale(scale, scale);
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, bmp.getWidth(), bmp.getHeight());
g2.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
GeneralPath path = new GeneralPath();
for (PathElement pathElement : al) {
switch (pathElement.getType()) {
case CLOSE_PATH:
path.closePath();
break;
case LINE_TO:
path.lineTo(pathElement.getP0x(), pathElement.getP0y());
break;
case MOVE_TO:
path.moveTo(pathElement.getP0x(), pathElement.getP0y());
break;
case CURVE_TO:
path.curveTo(pathElement.getP0x(), pathElement.getP0y(), pathElement.getP1x(), pathElement.getP1y(), pathElement.getP2x(), pathElement.getP2y());
break;
}
}
g2.setPaint(Color.black);
g2.fill(path);
}
}
Thanks in advance.
We can convert this object:
GeneralPath path = new GeneralPath();
to SVGPath using Apache Batik
Add dependencies into pom.xml
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-anim</artifactId>
<version>1.9</version>
</dependency>
Create methods:
private static SVGOMDocument createSvgDocument(int width, int height) {
DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
SVGOMDocument document = (SVGOMDocument) domImpl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
Element svgTag = document.getRootElement();
svgTag.setAttribute("width", String.valueOf(width));
svgTag.setAttribute("height", String.valueOf(height));
return document;
}
private static void putPathToSvgDocument(SVGOMDocument document, GeneralPath path) {
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
SVGPath svgPath = new SVGPath(ctx);
Element svgElement = svgPath.toSVG(path);
svgElement.setAttribute("fill", "#000");
document.getRootElement().appendChild(svgElement);
}
private static void saveSvgDocumentToFile(SVGOMDocument document, File file) throws FileNotFoundException, IOException {
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
try (Writer out = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) {
svgGenerator.stream(document.getDocumentElement(), out);
}
}
Almost ready. Now we can create SVG file with our GeneralPath inside
GeneralPath path = new GeneralPath();
...
// TODO: set your image width and height ;)
SVGOMDocument document = createSvgDocument(1000,1000);
putPathToSvgDocument(document, path);
// TODO: set your filename :)
saveSvgDocumentToFile(document, new File("c:\\temp\\result.svg"));
Done!
Anyone looking forward to an answer to this would probably be someone like me. Newbie into image processing and looking for libraries that can quench the thirsts of your expedition. I haven't found solution to this problem and I cannot guide you anyone wrongly that what else can be done.
I observed that most of the image processing libraries are written in C++. Fewer libraries are there in Java. Why? Performance. And so for anyone who wants to produce efficient code and all the algorithms input be appreciated should use C++ too.
I want above solution for my Android application. I am going to write code in C++ and connect it via Android NDK.
For all other lost souls, be brave and take this decision. Here is my evidence to prove that.
I'm currently working on a screen recorder application that uses JNA to capture the frames of the video. I want to insert a semi-transparent marker onto the captured hbitmap before adding the cursor to better highlight the location of the cursor in the video. After much searching I haven't been able to find an example of how to get the pixel data out of, say, a BufferedImage and write it manually to the object pointed to by hbitmap. Here is my best attempt:
GuiTest.java:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
public class GuiTest extends JFrame
{
private static final long serialVersionUID = 1L;
private JLabel lblNewLabel;
public GuiTest()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initGui();
}
private void initGui()
{
setPreferredSize(new Dimension(400, 400));
Rectangle area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage bi = new BufferedImage(area.width, area.height, BufferedImage.TYPE_INT_ARGB);
int[] ia = new int[area.width * area.height];
bi.getRGB(0, 0, area.width, area.height, ia, 0, area.width);
MemoryImageSource mis = new MemoryImageSource(area.width, area.height, ia, 0, area.width);
mis.setAnimated(true);
mis.newPixels(captureScreen(area), ColorModel.getRGBdefault(), 0, area.width);
lblNewLabel = new JLabel("");
lblNewLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(mis)));
getContentPane().add(lblNewLabel, BorderLayout.CENTER);
}
public int[] captureScreen(Rectangle recordArea)
{ // This code captures the desktop. I wish I could say I understood it.
Pointer handle = User32.INSTANCE.GetDesktopWindow();
Pointer hdcSrc = User32.INSTANCE.GetWindowDC(handle);
Pointer hdcDest = Gdi32.INSTANCE.CreateCompatibleDC(hdcSrc);
Pointer hBitmap = Gdi32.INSTANCE.CreateCompatibleBitmap(hdcSrc, recordArea.width, recordArea.height);
Pointer hOld = Gdi32.INSTANCE.SelectObject(hdcDest, hBitmap);
Gdi32.INSTANCE.BitBlt(hdcDest, 0, 0, recordArea.width, recordArea.height, hdcSrc, 0, 0, Gdi32.SOURCE_COPY | Gdi32.CAPTURE_BLT);
try
{ // Start broken code:
BufferedImage MOUSE_SHADE = ImageIO.read(GuiTest.class.getResource("mouse_shade48.png"));
Point cursorPosition = MouseInfo.getPointerInfo().getLocation();
int[] pixels = MOUSE_SHADE.getRGB(0, 0, 48, 48, new int[48 * 48], 0, 48);
Memory shadeMem = new Memory(recordArea.width * recordArea.height * 4);
shadeMem.write(0, copyToOffsetCentered(pixels, new Rectangle(48, 48),
new int[recordArea.width * recordArea.height], new Rectangle(recordArea.width, recordArea.height),
cursorPosition.x, cursorPosition.y), 0, recordArea.width * recordArea.height);
BitmapInfo shadebmi = new BitmapInfo(1);
shadebmi.bmiHeader.biWidth = recordArea.width;
shadebmi.bmiHeader.biHeight = -recordArea.height;
shadebmi.bmiHeader.biPlanes = 1;
shadebmi.bmiHeader.biBitCount = 32;
shadebmi.bmiHeader.biCompression = Gdi32.BI_RGB;
Pointer hdc = User32.INSTANCE.GetDC(null);
Pointer hdcDest2 = Gdi32.INSTANCE.CreateCompatibleDC(hdc);
Pointer hBitmap2 = Gdi32.INSTANCE.CreateCompatibleBitmap(hdc, recordArea.width, recordArea.height);
Gdi32.INSTANCE.SetDIBits(hdc, hBitmap2, 0, recordArea.height, shadeMem, shadebmi, Gdi32.DIB_RGB_COLORS);
Msimg32.INSTANCE.TransparentBlt(hdcDest, 0, 0, recordArea.width, recordArea.height, hdc, 0, 0, recordArea.width, recordArea.height, new BlendFunction());
// I have also tried Gdi32.BitBlt
}
catch (IOException e1)
{
e1.printStackTrace();
} // End broken code
try
{
CursorData cursorData = new CursorData();
cursorData.drawCursorToHandle(hdcDest);
cursorData.close();
}
catch (Exception e)
{
e.printStackTrace();
}
Gdi32.INSTANCE.SelectObject(hdcDest, hOld);
Gdi32.INSTANCE.DeleteDC(hdcDest);
int nBits = recordArea.width * recordArea.height * 4;
BitmapInfo bmi = new BitmapInfo(1);
bmi.bmiHeader.biWidth = recordArea.width;
bmi.bmiHeader.biHeight = -recordArea.height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = Gdi32.BI_RGB;
Memory colorBitsMem = new Memory(nBits);
Gdi32.INSTANCE.GetDIBits(hdcSrc, hBitmap, 0, recordArea.height, colorBitsMem, bmi, Gdi32.DIB_RGB_COLORS);
int[] colorBits = colorBitsMem.getIntArray(0, recordArea.width * recordArea.height);
User32.INSTANCE.ReleaseDC(handle, hdcSrc);
Gdi32.INSTANCE.DeleteObject(hBitmap);
return colorBits;
}
private int[] copyToOffsetCentered(int[] src, Rectangle srcDim, int[] dest, Rectangle destDim, int dx, int dy)
{
int startx = dx - srcDim.width / 2;
int endx = startx + srcDim.width;
int starty = dy - srcDim.height / 2;
int endy = starty + srcDim.height;
for (int x = Math.max(startx, 0); x < Math.min(endx, destDim.width); x++)
{
for (int y = Math.max(starty, 0); y < Math.min(endy, destDim.height); y++)
{
int xSrc = x - startx;
int ySrc = y - starty;
dest[y * destDim.width + x] = src[ySrc * srcDim.width + xSrc];
}
}
return dest;
}
/**
* Launch the application.
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
GuiTest window = new GuiTest();
window.pack();
window.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
CursorData.java:
import java.awt.MouseInfo;
import java.awt.Point;
import com.sun.jna.Pointer;
public class CursorData
{
public boolean isVisible;
public Pointer iconHandle;
public Point position;
// private static final HBITMAP MOUSE_SHADE;
// static
// {
// BufferedImage bi = new BufferedImage(0, 0, 0);
// try
// {
// bi = ImageIO.read(Images.class.getResource("/icons/com/valsphere/mouse_shade48.png"));
// }
// catch (IOException e)
// {
// e.printStackTrace();
// }
// int[] pixels = bi.getRaster().getPixels(0, 0, 48, 48, new int[48 * 48]);
//
// }
public CursorData()
{
updateCursorData();
}
public void updateCursorData()
{
CursorInfo cursorInfo = new CursorInfo();
if (User32.INSTANCE.GetCursorInfo(cursorInfo))
{
isVisible = cursorInfo.flags.intValue() == User32.CURSOR_SHOWING;
if (isVisible)
{
iconHandle = User32.INSTANCE.CopyIcon(cursorInfo.hCursor.getPointer());
IconInfo iconInfo = new IconInfo();
if (User32.INSTANCE.GetIconInfo(iconHandle, iconInfo))
{
Point cursorPosition = MouseInfo.getPointerInfo().getLocation();
position = new Point(cursorPosition.x - iconInfo.xHotspot.intValue(), cursorPosition.y - iconInfo.yHotspot.intValue());
if (iconInfo.hbmMask != null)
{
Gdi32.INSTANCE.DeleteObject(iconInfo.hbmMask.getPointer());
}
if (iconInfo.hbmColor != null)
{
Gdi32.INSTANCE.DeleteObject(iconInfo.hbmColor.getPointer());
}
}
}
}
}
public void drawCursorToHandle(Pointer hdcDest)
{
drawCursorToHandle(hdcDest, new Point());
}
public void drawCursorToHandle(Pointer hdcDest, Point cursorOffset)
{
if (iconHandle != null)
{
Point drawPosition = new Point(position.x - cursorOffset.x, position.y - cursorOffset.y);
User32.INSTANCE.DrawIconEx(hdcDest, drawPosition.x, drawPosition.y, iconHandle, 0, 0, 0, null, User32.DI_NORMAL);
}
}
public void close() throws Exception
{
if (iconHandle != null)
{
User32.INSTANCE.DestroyIcon(iconHandle);
iconHandle = null;
}
}
}
mouse_shade48.png:
I have verified that my int[] is being created and populated correctly, I just don't know how to get that data into an hbitmap and properly alpha blend it with the captured screen hbitmap that I already have. What I'm currently seeing is that the image is showing up as though the broken code is being skipped. Any help is much appreciated.
Also, I realize that the code above is missing the Msimg32, Gdi32, and User32 JNA interfaces I'm using, so if you need them I can create a gist and put all the pertinent code there.
This question is continued from this thread. I could download the com.larmor.opencv.MatchTemplate library from here (in case anyone needs the link in future.)
Now coming down to my question.
When I run the code I am getting null pointer exception.
Attaching the error messages for reference.
Also this is the exact same project downloaded from here So I am not mentioning the code in the question.
Can anyone help me with how do I get rid of these errors.
EDIT
I had provided a link to the code above. So I didn't paste the entire code here.
Anyways, here's the code:
package templatematching;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Panel;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.media.Buffer;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.Player;
import javax.media.control.FrameGrabbingControl;
import javax.media.format.VideoFormat;
import javax.media.util.BufferToImage;
import javax.swing.JButton;
import javax.swing.JComponent;
import com.larmor.opencv.MatchTemplate;
/**
* This example match WebCam Area.
* <br>
* The class uses Java Media Framework API (JMF),
* and JAI-Java Image I/O Technology
* and Java Media Framework API (JMF)
* (for dowload: https://jai.dev.java.net/,
* https://jai-imageio.dev.java.net/ and
* http://java.sun.com/products/java-media/jmf/2.1.1/download.html).<br>
* <br>
*
* 1) press button "select area" for stop image
* 2) using mouse select area to matching (red rectangle)
* 3) press button "start matching" for play webcam and starting matching (green rectangle)
*
* #author Pier Paolo Ciarravano Larmor
* License: GNU General Public License
* #version Vers. 0.5 beta (April.2008)
*/
public class TestMatchWebCamArea extends Panel implements ActionListener,
MouseListener, MouseMotionListener {
static Player player = null;
CaptureDeviceInfo di = null;
MediaLocator ml = null;
JButton matchCam = null;
JButton selectArea = null;
Component sourceComp = null;
DoubleBufferPanel dbPanel = null;
int areaX = 0;
int areaY = 0;
int areaWidth = 0;
int areaHeight = 0;
int stateButton;
BufferedImage buffImgTemplate = null;
public TestMatchWebCamArea() {
stateButton = 0;
setLayout(new BorderLayout());
// setSize(320,240);
selectArea = new JButton("Select Area");
selectArea.addActionListener(this);
matchCam = new JButton("Start Matching");
matchCam.addActionListener(this);
matchCam.setEnabled(false);
// String str1 = "vfw:Logitech USB Video Camera:0";
String str2 = "vfw:Microsoft WDM Image Capture (Win32):0";
di = CaptureDeviceManager.getDevice(str2);
ml = di.getLocator();
try {
Manager.setHint(Manager.LIGHTWEIGHT_RENDERER, Boolean.TRUE);
player = Manager.createRealizedPlayer(ml);
player.start();
if ((sourceComp = player.getVisualComponent()) != null) {
sourceComp.addMouseMotionListener(this);
sourceComp.addMouseListener(this);
// sourceComp.setBounds (0, 0,
// sourceComp.getPreferredSize().width,
// sourceComp.getPreferredSize().height);
dbPanel = new DoubleBufferPanel();
dbPanel.setLayout(new BorderLayout());
dbPanel.add(sourceComp);
// dbPanel.setBackground(Color.BLUE);
// dbPanel.setBounds (0, 0, sourceComp.getPreferredSize().width,
// sourceComp.getPreferredSize().height);
add(dbPanel, BorderLayout.CENTER);
}
add(selectArea, BorderLayout.NORTH);
add(matchCam, BorderLayout.SOUTH);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Frame f = new Frame("Test Match WebCam - By Larmor");
f.setResizable(false);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
playerclose();
System.exit(0);
}
});
TestMatchWebCamArea cf = new TestMatchWebCamArea();
f.add("Center", cf);
f.pack();
// f.setSize(new Dimension(320,240));
f.setVisible(true);
}
public static void playerclose() {
player.close();
player.deallocate();
}
public BufferedImage getPlayerImage() {
// Grab a frame
FrameGrabbingControl fgc = (FrameGrabbingControl) player.getControl("javax.media.control.FrameGrabbingControl");
Buffer buf = fgc.grabFrame();
// Convert it to an image
BufferToImage btoi = new BufferToImage((VideoFormat) buf.getFormat());
Image img = btoi.createImage(buf);
// Convert to BufferedImage
BufferedImage bi = new BufferedImage(sourceComp.getPreferredSize().width, sourceComp.getPreferredSize().height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = bi.createGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return bi;
}
public void actionPerformed(ActionEvent e) {
JComponent c = (JComponent) e.getSource();
if (c == selectArea) {
stateButton = 1;
player.stop();
selectArea.setEnabled(false);
} else if (c == matchCam) {
// Validate Area
if ((areaX >= 0)
&& (areaY > 0)
&& (areaWidth > 0)
&& (areaHeight > 0)) {
buffImgTemplate = getPlayerImage().getSubimage(areaX, areaY, areaWidth, areaHeight);
MatchTemplate.displayBufferedImage(buffImgTemplate, "template");
stateButton = 2;
player.start();
matchCam.setEnabled(false);
selectArea.setEnabled(true);
}
}
}
// #Override
public void mouseClicked(MouseEvent arg0) {
}
// #Override
public void mouseEntered(MouseEvent arg0) {
}
// #Override
public void mouseExited(MouseEvent arg0) {
}
// #Override
public void mouseMoved(MouseEvent arg0) {
}
// #Override
public void mousePressed(MouseEvent arg0) {
arg0.consume();
if (stateButton == 1) {
areaX = arg0.getX();
areaY = arg0.getY();
dbPanel.repaint();
}
}
// #Override
public void mouseReleased(MouseEvent arg0) {
arg0.consume();
if (stateButton == 1) {
areaWidth = arg0.getX() - areaX;
areaHeight = arg0.getY() - areaY;
dbPanel.repaint();
matchCam.setEnabled(true);
}
}
// #Override
public void mouseDragged(MouseEvent arg0) {
arg0.consume();
if (stateButton == 1) {
areaWidth = arg0.getX() - areaX;
areaHeight = arg0.getY() - areaY;
dbPanel.repaint();
}
}
class DoubleBufferPanel extends Panel {
Image offscreen;
/**
* null out the offscreen buffer as part of invalidation
*/
public void invalidate() {
super.invalidate();
offscreen = null;
}
/**
* override update to *not* erase the background before painting
*/
public void update(Graphics g) {
paint(g);
}
/**
* paint children into an offscreen buffer, then blast entire image at once.
*/
public void paint(Graphics g) {
if (offscreen == null) {
offscreen = createImage(getSize().width, getSize().height);
}
Graphics og = offscreen.getGraphics();
og.setClip(0, 0, getSize().width, getSize().height);
super.paint(og);
if (stateButton == 1) {
Graphics2D g2 = (Graphics2D) og;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.red);
Rectangle2D e = new Rectangle2D.Float(areaX, areaY, areaWidth, areaHeight);
g2.draw(e);
} else if (stateButton == 2) {
// match template
BufferedImage buffImgSource = getPlayerImage();
long startTime = System.currentTimeMillis();
MatchTemplate matchObj = new MatchTemplate(buffImgSource, buffImgTemplate);
Point matchPoint = matchObj.matchTemplateBestPoint(MatchTemplate.CV_TM_CCOEFF_NORMED);
long totalTime = System.currentTimeMillis() - startTime;
System.out.println("totalTime for matching:" + totalTime);
Graphics2D g2 = (Graphics2D) og;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.green);
Rectangle2D e = new Rectangle2D.Float(matchPoint.x, matchPoint.y, areaWidth, areaHeight);
g2.draw(e);
}
g.drawImage(offscreen, 0, 0, null);
og.dispose();
}
}
}
The null pointer is caused by the following lines in the constructor of TestMatchWebCamArea:
String str2 = "vfw:Microsoft WDM Image Capture (Win32):0";
di = CaptureDeviceManager.getDevice(str2);
ml = di.getLocator(); //NullPointerException
The only way you can get a NullPointerException on that line is if di == null, so we look at where di is set, and look at the relevant Javadoc:
Returns:
A CaptureDeviceInfo object that corresponds to the specified device name. Returns null if the specified device could not be found.
It seems therefore that the method can't find a device with the name "vfw:Microsoft WDM Image Capture (Win32):0".
I've never used the Java Media Framework, but the example given for the method you're calling doesn't look anything like the device name you've provided:
Parameters:
deviceName - A String that contains the name of the device for which you want to get a CaptureDeviceInfo object. For example: "SunVideo"
I'm making a fun little test screen recording program in java, and I want it to have a preview of your screen before you start recording.. but its a very slow and poor method of which I am using, involving capturing an image, saving it, then reading it in through a bufferedimage and drawing that image using Graphics. Its very slow and not useful as a "preview" is there a way to speed up and have a more efficient "previewing system".
Here is what I have so far:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MainFrame implements ActionListener, Runnable {
//add frame components
public static JFrame frame = new JFrame("Screen Caper - v1.0.1");
JButton start = new JButton("record");
JButton close = new JButton("Exit");
JPanel preview = new JPanel();
public static boolean running = false;
public static boolean recording = false;
public static boolean paused = false;
public static String curDir = System.getProperty("user.dir");
//get the screen width
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth();
double height = screenSize.getHeight();
Container a = new Container();
Container b = new Container();
public MainFrame() {
frame.setSize((int)(width) - 80, (int)(height) - 80);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//setup the buttons and JPanel
a.setLayout(new GridLayout(1, 2));
a.add(start);
start.addActionListener(this);
a.add(close);
close.addActionListener(this);
frame.add(a, BorderLayout.NORTH);
b.setLayout(new GridLayout(1, 2));
b.add(preview);
frame.add(b, BorderLayout.CENTER);
//add anything else
running = true;
//set frame to visible
frame.setVisible(true);
run();
}
public static void main(String[] args) {
new MainFrame();
}
public void run() {
Graphics g = frame.getGraphics();
while (running) {
//draw the preview of the computer screen on the JPanel if its not recording already
if (!recording && !paused) {
drawPreview(g);
}
}
}
public void drawPreview(Graphics g) {
BufferedImage image;
try {
image = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
ImageIO.write(image, "png", new File("test.png"));
} catch (Exception ex) {
ex.printStackTrace();
}
BufferedImage prevIm;
try {
prevIm = ImageIO.read(new File("test.png"));
g.setColor(new Color(0, 0, 0));
g.fillRect(preview.getX() + 3, preview.getY() + 51, preview.getWidth(), preview.getHeight() + 1);
g.drawImage(prevIm, preview.getX() + 3, preview.getY() + 51, preview.getX() + preview.getWidth(), preview.getY() + preview.getHeight(), null);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void record(Graphics g) {
}
#Override
public void actionPerformed(ActionEvent event) {
if (event.getSource().equals(start)) {
if (!recording) {
//if the program isn't recording, then start recording
Graphics g = frame.getGraphics();
record(g);
start.setText("Finish");
recording = true;
System.out.println("recording...");
} else {
//else stop recording
start.setText("record");
recording = false;
System.out.println("done");
}
}
if (event.getSource().equals(close)) {
paused = true;
int ans = JOptionPane.showConfirmDialog(null, "Woah there! You're about to quit the application\nAre you sure you want to procced?", "Caution!", JOptionPane.YES_NO_OPTION);
if (ans == JOptionPane.YES_OPTION) {
System.exit(0);
} else if (ans == JOptionPane.NO_OPTION) {
paused = false;
}
}
}
}
any help is appreciated!
Don't use getGraphics, this is not how custom painting is done.
Your run method may simply be running to fast, consider using a javax.swing.Timer instead
Toolkit.getDefaultToolkit().getScreenSize() only returns the "default" screen and does not take into consideration split screens
Capturing the screen is a time consuming process and there's not much you can do about reducing it (as it's outside of your control). You "could" setup a series of Threads whose job it was to capture a given section of the desktop. You could also achieve this using SwingWorkers which would make it easier to sync the updates back to the UI...
Take a look at:
Performing Custom Painting
Concurrency in Swing
How to Use Swing Timers
Updated with example
This is a proof of concept only! You should understand what it's trying to do and borrow ideas from it...
You can see the preview window change inside the preview window...kinda freaky...
It tested this on a virtual desktop of 4480x1600.
Basically, it divides the desktop up into a 4x4 grid and starts a Thread for each section. Each thread is responsible for capturing it's own section of the screen.
It also scales that resulting image down and feeds it back to the UI.
I had started with SwingWorkers, but it seems to be hard to be limited to 10 threads. You could reduce the grid size and use SwingWorkers, they tend to be simpler to use and manage then raw Threads.
Each section is given an id, which allows me keep track of what's changed. Technically, you could just add elements to a List, but how do you determine what's new and what's old?
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PreviewDesktop {
public static void main(String[] args) {
new PreviewDesktop();
}
public PreviewDesktop() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements Puzzler {
private Rectangle virtualBounds;
private double scale;
private Map<Integer, PuzzlePiece> pieces;
public TestPane() {
virtualBounds = getVirtualBounds();
int columns = 4;
int rows = 4;
pieces = new HashMap<>(columns * rows);
int columnWidth = Math.round(virtualBounds.width / (float) columns);
int rowHeight = Math.round(virtualBounds.height / (float) rows);
int id = 0;
for (int row = 0; row < rows; row++) {
int y = virtualBounds.y + (row * rowHeight);
for (int column = 0; column < columns; column++) {
int x = virtualBounds.x + (column * columnWidth);
Rectangle bounds = new Rectangle(x, y, columnWidth, rowHeight);
GrabberWorker worker = new GrabberWorker(id, this, bounds);
System.out.println(id);
id++;
startThread(worker);
}
}
}
#Override
public double getScale() {
return scale;
}
#Override
public void invalidate() {
super.invalidate();
scale = getScaleFactorToFit(virtualBounds.getSize(), getSize());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Integer id : pieces.keySet()) {
PuzzlePiece piece = pieces.get(id);
Rectangle bounds = piece.getBounds();
BufferedImage img = piece.getImage();
g2d.drawImage(img, bounds.x, bounds.y, this);
// If you want to see each sections bounds, uncomment below...
//g2d.draw(bounds);
}
g2d.dispose();
}
#Override
public void setPiece(int id, PuzzlePiece piece) {
pieces.put(id, piece);
repaint();
}
protected void startThread(GrabberWorker worker) {
Thread thread = new Thread(worker);
thread.setDaemon(true);
thread.start();
}
}
public class PuzzlePiece {
private final Rectangle bounds;
private final BufferedImage img;
public PuzzlePiece(Rectangle bounds, BufferedImage img) {
this.bounds = bounds;
this.img = img;
}
public Rectangle getBounds() {
return bounds;
}
public BufferedImage getImage() {
return img;
}
}
public interface Puzzler {
public void setPiece(int id, PuzzlePiece piece);
public double getScale();
}
public class GrabberWorker implements Runnable {
private Rectangle bounds;
private Puzzler puzzler;
private int id;
private volatile PuzzlePiece parked;
private ReentrantLock lckParked;
public GrabberWorker(int id, Puzzler puzzler, Rectangle bounds) {
this.id = id;
this.bounds = bounds;
this.puzzler = puzzler;
lckParked = new ReentrantLock();
}
protected void process(PuzzlePiece piece) {
// puzzler.setPiece(bounds, chunks.get(chunks.size() - 1));
puzzler.setPiece(id, piece);
}
protected void publish(PuzzlePiece piece) {
lckParked.lock();
try {
parked = piece;
} finally {
lckParked.unlock();
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
lckParked.lock();
try {
process(parked);
} finally {
lckParked.unlock();
}
}
});
}
#Override
public void run() {
try {
Robot bot = new Robot();
while (true) {
BufferedImage img = bot.createScreenCapture(bounds);
double scale = puzzler.getScale();
Rectangle scaled = new Rectangle(bounds);
scaled.x *= scale;
scaled.y *= scale;
scaled.width *= scale;
scaled.height *= scale;
BufferedImage imgScaled = getScaledInstance(img, scale);
publish(new PuzzlePiece(scaled, imgScaled));
Thread.sleep(500);
}
} catch (AWTException | InterruptedException exp) {
exp.printStackTrace();
}
}
}
public static Rectangle getVirtualBounds() {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
bounds.add(gd.getDefaultConfiguration().getBounds());
}
return bounds;
}
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = (double) iTargetSize / (double) iMasterSize;
return dScale;
}
public static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
return getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
}
protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean bHighQuality) {
BufferedImage imgScale = img;
int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);
// System.out.println("Scale Size = " + iImageWidth + "x" + iImageHeight);
if (dScaleFactor <= 1.0d) {
imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
} else {
imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
}
return imgScale;
}
protected static BufferedImage getScaledDownInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
protected static BufferedImage getScaledUpInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality) {
w = img.getWidth();
h = img.getHeight();
} else {
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w < targetWidth) {
w *= 2;
if (w > targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h < targetHeight) {
h *= 2;
if (h > targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp = null;
} while (w != targetWidth || h != targetHeight);
return ret;
}
}
Now, if you're feeling really adventurous, you could update this idea so that if the underlying image for a section didn't change and the scale didn't change, it didn't send that to the UI, which might help reduce some of the overhead ... and no, I don't have code to do that ;)