Does anyone know how to capture a screen shot in Java (not it's own screen, but any other window on the desktop and they don't necessarily have to be the active window) in Windows? There are a number of threads here on this similar subject, but I have yet to find an answer.
I've tried using JNA, but became stuck after a few attempts. For example...
public class Main {
public static void main(String[] args) {
Main m = new Main();
List<WindowInfo> list = m.getWindows();
for (int i=0;i<list.size();i++)
{
WindowInfo info = list.get(i);
System.out.println(info.getTitle());
}
WindowInfo wi = list.get(0);
W32API.HDC hdcSrc = User32.instance.GetWindowDC(wi.getHwnd());
W32API.HDC hdcMemory = Gdi32.instance.CreateCompatibleDC(hdcSrc);
//W32API.HBITMAP hBitmapMemory = Gdi32.instance.CreateCompatibleBitmap(hdcSrc, int width, int height);
int width = wi.getRect().right - wi.getRect().left;
int height = wi.getRect().bottom - wi.getRect().top;
W32API.HBITMAP hBitmapMemory = Gdi32.instance.CreateCompatibleBitmap(hdcSrc, width, height);
W32API.HANDLE hOld = Gdi32.instance.SelectObject(hdcMemory, hBitmapMemory);
Gdi32.instance.BitBlt(hdcMemory, 0, 0, width, height, hdcSrc, width+2, height+2, 0x00CC0020);
/* # now how do we convert to a BufferedImage??? */
// clean up
Gdi32.instance.SelectObject(hdcMemory, hOld);
Gdi32.instance.DeleteDC(hdcMemory);
Gdi32.instance.DeleteObject(hBitmapMemory);
User32.instance.ReleaseDC(wi.getHwnd(), hdcSrc);
}
/**
*
* #return
*/
private List<WindowInfo> getWindows() {
final List<WindowInfo> list = new ArrayList<WindowInfo>();
User32.instance.EnumWindows(new WndEnumProc() {
public boolean callback(int hWnd, int lParam) {
if (User32.instance.IsWindowVisible(hWnd)) {
RECT r = new RECT();
User32.instance.GetWindowRect(hWnd, r);
byte[] buffer = new byte[1024];
User32.instance.GetWindowTextA(hWnd, buffer, buffer.length);
String title = Native.toString(buffer);
if (title!=null&&title.length()>0) {
list.add(new WindowInfo(hWnd, r, title));
}
}
return true;
}
}, 0);
Collections.sort(list, new Comparator<WindowInfo>() {
public int compare(WindowInfo o1, WindowInfo o2) {
int i1 = (o1.getTitle()!=null&&o1.getTitle().length()>0?o1.getTitle():" ").charAt(0);
int i2 = (o2.getTitle()!=null&&o2.getTitle().length()>0?o2.getTitle():" ").charAt(0);
return i1 - i2;
}
});
return list;
}
}
I've also tried the equivalent of "PrintWindow()" API...
Graphics g = form.CreateGraphics();
Bitmap bmp = new Bitmap(form.Size.Width, form.Size.Height, g);
Graphics memoryGraphics = Graphics.FromImage(bmp);
IntPtr dc = memoryGraphics.GetHdc();
bool success = PrintWindow(form.Handle, dc, 0);
memoryGraphics.ReleaseHdc(dc);
// bmp now contains the screenshot
Or do I have to use JNI, or any other tool?
Here's a working example.
The application being captured can't be minimized but it doesn't need to have focus or be on top (i.e. visible).
The code provided in the related C# thread, the MSDN article Capturing an Image and jmemoryeditorw provided the necessary pieces.
The code uses GetDC and GetClientRect to capture the client area of the window. They can be replaced by GetWindowDC and GetWindowRect if you want to capture the whole window including window decorations.
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import jna.extra.GDI32Extra;
import jna.extra.User32Extra;
import jna.extra.WinGDIExtra;
import com.sun.jna.Memory;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HBITMAP;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.platform.win32.WinGDI;
import com.sun.jna.platform.win32.WinGDI.BITMAPINFO;
import com.sun.jna.platform.win32.WinNT.HANDLE;
public class Paint extends JFrame {
public BufferedImage capture(HWND hWnd) {
HDC hdcWindow = User32.INSTANCE.GetDC(hWnd);
HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow);
RECT bounds = new RECT();
User32Extra.INSTANCE.GetClientRect(hWnd, bounds);
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow, width, height);
HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
GDI32Extra.INSTANCE.BitBlt(hdcMemDC, 0, 0, width, height, hdcWindow, 0, 0, WinGDIExtra.SRCCOPY);
GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);
GDI32.INSTANCE.DeleteDC(hdcMemDC);
BITMAPINFO bmi = new BITMAPINFO();
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
Memory buffer = new Memory(width * height * 4);
GDI32.INSTANCE.GetDIBits(hdcWindow, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, width, height, buffer.getIntArray(0, width * height), 0, width);
GDI32.INSTANCE.DeleteObject(hBitmap);
User32.INSTANCE.ReleaseDC(hWnd, hdcWindow);
return image;
}
public static void main(String[] args) {
new Paint();
}
BufferedImage image;
public Paint() {
HWND hWnd = User32.INSTANCE.FindWindow(null, "Untitled - Notepad");
this.image = capture(hWnd);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setExtendedState(MAXIMIZED_BOTH);
setVisible(true);
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(image, 20, 40, null);
}
}
I had to define some extra functions that weren't included in platform.jar (which can be found on the JNA website).
package jna.extra;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.win32.W32APIOptions;
public interface GDI32Extra extends GDI32 {
GDI32Extra INSTANCE = (GDI32Extra) Native.loadLibrary("gdi32", GDI32Extra.class, W32APIOptions.DEFAULT_OPTIONS);
public boolean BitBlt(HDC hObject, int nXDest, int nYDest, int nWidth, int nHeight, HDC hObjectSource, int nXSrc, int nYSrc, DWORD dwRop);
}
package jna.extra;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.win32.W32APIOptions;
public interface User32Extra extends User32 {
User32Extra INSTANCE = (User32Extra) Native.loadLibrary("user32", User32Extra.class, W32APIOptions.DEFAULT_OPTIONS);
public HDC GetWindowDC(HWND hWnd);
public boolean GetClientRect(HWND hWnd, RECT rect);
}
package jna.extra;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinGDI;
public interface WinGDIExtra extends WinGDI {
public DWORD SRCCOPY = new DWORD(0x00CC0020);
}
Use java.awt.Robot.createScreenCapture().
Here's an example:
try {
Robot robot = new Robot();
Rectangle size = new Rectangle(Toolkit.getDefaultToolkit()
.getScreenSize());
BufferedImage buf = robot.createScreenCapture(size);
ImageIO.write(buf, "png", new File("d:/test.png"));
} catch (AWTException ae) {
throw new RuntimeException("something went wrong");
}
Code originally stolen from here.
For your original question, here it goes.
Capturing an inactive window in Windows is pretty straightforward, using the robot class, ONLY and ONLY if the window is visible at the moment of capturing. If you want to avoid that requirement, you HAVE to use the DWM API.
Using the normal Windows API (pre Vista), you can use GetWindowRect(handle,RECT) where handle is a handler to the window you want to capture. This will get you a RECT object (I assume you are using JNA), here is the sequence of code you should write:
RECT dimensionsOfWindow = new RECT();
GetWindowRect( handlerToWindow, dimensionsOfWindow );//now in the dimensionsOfWindow you have the dimensions
Robot robot = new Robot();
BufferedImage img = robot.createScreenCapture( dimensionsOfWindow.toRectangle() );//now in the img object you have only the image of your desired window
However!! This will work as a charm ONLY if your window is currently visible. If it is minimized, you will get some exception in java (because it has negative x and y ). And if it is partially hidden, you will also screenshot the other windows that are on top of it.
You can't solve your problem on boxes that don't have dwm (Desktop Windows Manager) as it has an API that allows different windows to write to a temp buffer before they actually are painted to the screen.
On XP and non - running DWM machines, however, you are stuck with the code I gave you.
Additionally , you can take a look at the following question:
link text
Edit:
Here is an interesting guide (in C#, though, but you can use JNA+Java applying the same principles) that will give you a better understanding of the DWM and how to use it to do EXACTLY what you want.
link text
EditEdit
Just saw you have a link to the same guide in C# that I gave you. What seems to be the problem in just rewriting the code for Java/JNA?
EditEditEdit
To answer your additional question (how to convert your BitBit to a BufferedImage ), here is a guy who did it in his Open Source project. It is a nice piece of work and give him some appreciation:
http://code.google.com/p/jmemoryeditorw/
You might notice that if you run the program, it will give you all the processes and also...their Icons. If you dig in the code, you will see how they are converted from BitBit to BufferedImages.
Cheers and I have to say, a very nice question.
Related
I created a class called VainillaImagen:
public VainillaImage(String url){
this.icimg=new ImageIcon(url);
this.imagen=new JLabel(this.icimg);
this.imagen.setVisible(true);
}
and then I created a methos called setDimensions that use another method called resizeVainillaImg. But the resizeVainillaImg method dont work any ideas why?
public void setDimensions(boolean wRel,int width,boolean hRel,int height){
Dimension dimPantalla = Toolkit.getDefaultToolkit().getScreenSize();
int nwidth,nheight;
if(wRel){
nwidth=(int)(width*(dimPantalla.width));
}else{
nwidth=width;
}
if(hRel){
nheight=(int)(height*(dimPantalla.height));
}else{
nheight=height;
}
resizeVainillaImg(nwidth,nheight);
}
public void resizeVainillaImg(int newWidth,int newHeight){
Image img = this.icimg.getImage();
BufferedImage bi = new BufferedImage(newWidth,newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, newWidth, newHeight,null);
g.dispose();
this.icimg = new ImageIcon(bi);
this.imagen.setIcon(this.icimg);
}
Although I didn't understand the setDimensions(), but I think you are trying to fit your image into screen width and height.
By multiplying int values of width and height in setDimensions(), you will simply be able to multiply small int numbers. For bigger numbers you will run out of memory because of huge image size (widthscreenwidth , heightscreenheight).
Lets assume you want to resize your image to percent of your screen, or use the default height and with of the image. Using the code below, pass negative number (-1 for example) to ignore the screen size, and 0> number to resize it to percent of screen.
I hope this help. However, it you have some other think in your mind, just remember to use float because of int * int multiplications :)
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
/**
*
* #author Pasban
*/
public class VainillaImage {
private ImageIcon icimg;
private JLabel imagen;
public static void main(String args[]) {
JDialog d = new JDialog();
VainillaImage v = new VainillaImage("92-1024x576.jpg");
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
d.getContentPane().add(v.imagen);
v.setDimensions(-1, 1);
d.pack();
d.setLocationRelativeTo(null);
d.setVisible(true);
}
public void setDimensions(double width, double height) {
Dimension dimPantalla = Toolkit.getDefaultToolkit().getScreenSize();
int nwidth, nheight;
nwidth = (int) (width * (dimPantalla.width));
nheight = (int) (height * (dimPantalla.height));
resizeVainillaImg(nwidth, nheight);
}
public void resizeVainillaImg(int newWidth, int newHeight) {
Image img = this.icimg.getImage();
newWidth = Math.max(newWidth, img.getHeight(null));
newWidth = Math.max(newHeight, img.getHeight(null));
BufferedImage bi = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, newWidth, newHeight, null);
g.dispose();
this.icimg = new ImageIcon(bi);
this.imagen.setIcon(this.icimg);
}
public VainillaImage(String url) {
this.icimg = new ImageIcon(url);
this.imagen = new JLabel(this.icimg);
this.imagen.setVisible(true);
}
}
If you are trying to dynamically resize an Icon based on the space available to the label then check out Darryl's Stretch Icon.
I am newly working with openCV in java on eclipse and am working on an eye tracking software, i am using a base code some one else has created and plan to tweek it but am having an error with a few lines of code and can not figure out why. here is the entire class
package have;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
class FacePanel extends JPanel{
private static final long serialVersionUID = 1L;
private BufferedImage image;
// Create a constructor method
public FacePanel(){
super();
}
/*
* Converts/writes a Mat into a BufferedImage.
*
* #param matrix Mat of type CV_8UC3 or CV_8UC1
* #return BufferedImage of type TYPE_3BYTE_BGR or TYPE_BYTE_GRAY
*/
public boolean matToBufferedImage(Mat matrix) {
MatOfByte mb=new MatOfByte();
Highgui.imencode(".jpg", matrix, mb);
try {
this.image = ImageIO.read(new ByteArrayInputStream(mb.toArray()));
} catch (IOException e) {
e.printStackTrace();
return false; // Error
}
return true; // Successful
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if (this.image==null) return;
g.drawImage(this.image,10,10,this.image.getWidth(),this.image.getHeight(), null);
}
}
class FaceDetector {
private CascadeClassifier face_cascade;
// Create a constructor method
public FaceDetector(){
// face_cascade=new CascadeClassifier("./cascades/lbpcascade_frontalface_alt.xml");
//..didn't have not much luck with the lbp
face_cascade=new CascadeClassifier("./cascades/haarcascade_frontalface_alt.xml");
if(face_cascade.empty())
{
System.out.println("--(!)Error loading A\n");
return;
}
else
{
System.out.println("Face classifier loooaaaaaded up");
}
}
public Mat detect(Mat inputframe){
Mat mRgba=new Mat();
Mat mGrey=new Mat();
MatOfRect faces = new MatOfRect();
inputframe.copyTo(mRgba);
inputframe.copyTo(mGrey);
Imgproc.cvtColor( mRgba, mGrey, Imgproc.COLOR_BGR2GRAY);
Imgproc.equalizeHist( mGrey, mGrey );
face_cascade.detectMultiScale(mGrey, faces);
System.out.println(String.format("Detected %s faces", faces.toArray().length));
for(Rect rect:faces.toArray())
{
Point center= new Point(rect.x + rect.width*0.5, rect.y + rect.height*0.5 );
//draw a blue eclipse around face
the error starts here with error code : "The constructor Size(double, double, int, int, int, Scalar) is undefined"
Size s = new Size( rect.width*0.5, rect.height*0.5), 0, 0, 360, new Scalar( 0, 0, 255 )
then here at ellipse i get an error of : "The method ellipse(Mat, RotatedRect, Scalar, int, int) in the type Core is not applicable for the arguments (Mat, Point, Size, int, int, int)
Core.ellipse( mRgba, center,s , 4, 8, 0 );
}
return mRgba;
}
}
public class Eyes {
public static void main(String arg[]) throws InterruptedException{
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//or ... System.loadLibrary("opencv_java244");
//make the JFrame
JFrame frame = new JFrame("WebCam Capture - Face detection");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FaceDetector faceDetector=new FaceDetector();
FacePanel facePanel = new FacePanel();
frame.setSize(400,400); //give the frame some arbitrary size
frame.setBackground(Color.BLUE);
frame.add(facePanel,BorderLayout.CENTER);
frame.setVisible(true);
//Open and Read from the video stream
Mat webcam_image=new Mat();
VideoCapture webCam =new VideoCapture(0);
if( webCam.isOpened())
{
Thread.sleep(500); /// This one-time delay allows the Webcam to initialize itself
while( true )
{
webCam.read(webcam_image);
if( !webcam_image.empty() )
{
Thread.sleep(200); /// This delay eases the computational load .. with little performance leakage
frame.setSize(webcam_image.width()+40,webcam_image.height()+60);
//Apply the classifier to the captured image
webcam_image=faceDetector.detect(webcam_image);
//Display the image
facePanel.matToBufferedImage(webcam_image);
facePanel.repaint();
}
else
{
System.out.println(" --(!) No captured frame from webcam !");
break;
}
}
}
webCam.release(); //release the webcam
} //end main
}
any and all help would be greatly appreciated
If you have a look at the OpenCV Java API you can see the defined constructors for Size and what parameters are required:
http://docs.opencv.org/java/org/opencv/core/Size.html
A size holds two values, height and width.
So your code:
Size s = new Size( rect.width*0.5, rect.height*0.5), 0, 0, 360, new Scalar( 0, 0, 255 )
should be:
Size s = new Size( rect.width*0.5, rect.height*0.5);
this creates a size holding half the rectangle width and half the rectangle height.
And again for the Ellipse methods:
http://docs.opencv.org/java/org/opencv/core/Core.html
There are many variations but it looks like you want to be using the following:
public static void ellipse(Mat img,
Point center,
Size axes,
double angle,
double startAngle,
double endAngle,
Scalar color)
So your code:
Core.ellipse( mRgba, center,s , 4, 8, 0 );
is likely just missing the colour scalar:
Core.ellipse( mRgba, center,s , 4, 8, 0, new Scalar(B,G,R));
Where B,G,R are doubles for each colour channel.
I'm using Java3D to visualize a room with some primitives in it. I have an image background that I tile so that it fills the entire frame using background.setImageScaleMode(Background.SCALE_REPEAT);. Now I would like to add another semitransparent background on top of this background, and I would like to stretch it to cover the screen using SCALE_FIT_ALL. This will create an image effect that I cannot otherwise achieve. However, when I try to do this Java 3D complains that Group.addChild: child already has a parent.
Other ways of doing the same thing without using backgrounds (e.g. draw it on a 2D primitive) would be a interest too.
So my question is how can I achieve what I want with Java3D?
MWE: Image are available here. I want to draw bg-stars.png with Background.SCALE_REPEAT and then on top of that bg-glow.png with Background.SCALE_FIT_ALL.
Probably not what you actually want to achieve, but too long for a comment:
I did a test of adding multiple Backgrounds, and it "worked" basically (that is: it did not cause an error message). But the documentation of Background says
If multiple Background nodes are active, the Background node that is "closest" to the eye will be used.
Thus, I assume that it is not possible at all to display multiple backgrounds simulataneously at all.
Depending on what you want to achieve, there are probably several possibilities. The following it one approach that might be "close" to what you want. But I am not familiar with Backgrounds in Java3D, and assume that there are more elegant, efficient, flexible (or simply: better) approaches (like creating a huge, semi-transparent quad with the overlay texture or whatever...)
However, the idea here was to create the background as a single image. Composing BufferedImages is rather easy and offers a lot of possibilities. So I'm taking the bg-stars.png image and create a "tiled" version of this image (large enough to fill a certain area - in practice, this could simply be made as large as the maximum screen size). Then I'm composing this with the "overlay" image, bg-glow.png, by just painting it over the tiled image.
The resulting image can then be used to create the Background.
At the first glance, the result may look like what you want to achieve, but of course, there may be some caveats. E.g. one has to think about how this could be implemented to adapt to changes of the window size. (Listening for this with a ComponentListener and updating the image would be easy, but ... well).
And again: There certainly are better solutions. But maybe this can at least serve as a workaround until you find the better solution.
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;
public class SimpleBackgroundTest extends Applet
{
private static final int WIDTH = 1200;
private static final int HEIGHT = 1200;
public static void main(String[] args) throws IOException
{
System.setProperty("sun.awt.noerasebackground", "true");
Frame frame = new MainFrame(new SimpleBackgroundTest(), WIDTH, HEIGHT);
}
public SimpleBackgroundTest()
{
setLayout(new BorderLayout());
Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
add("Center", c);
BranchGroup group = new BranchGroup();
group.addChild(createSomeCube());
BufferedImage stars = null;
BufferedImage glow = null;
try
{
stars = ImageIO.read(new File("bg-stars.png"));
glow = ImageIO.read(new File("bg-glow.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
BufferedImage tiled = createTiled(stars, WIDTH, HEIGHT);
BufferedImage overlay = createOverlay(tiled, glow);
Background background = createBackground(overlay);
group.addChild(background);
SimpleUniverse universe = new SimpleUniverse(c);
universe.addBranchGraph(group);
universe.getViewingPlatform().setNominalViewingTransform();
}
private static BufferedImage createTiled(
BufferedImage image, int targetSizeX, int targetSizeY)
{
BufferedImage result = new BufferedImage(
targetSizeX, targetSizeY,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
for (int x = 0; x < targetSizeX; x += image.getWidth())
{
for (int y = 0; y < targetSizeY; y += image.getHeight())
{
g.drawImage(image, x, y, null);
}
}
g.dispose();
return result;
}
private static BufferedImage createOverlay(
BufferedImage image, BufferedImage overlay)
{
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.drawImage(image, 0, 0, null);
g.drawImage(overlay, 0, 0, image.getWidth(), image.getHeight(), null);
g.dispose();
return result;
}
private static Background createBackground(BufferedImage image)
{
TextureLoader textureLoader = new TextureLoader(image);
ImageComponent2D imageComponent = textureLoader.getImage();
Background background = new Background();
background.setImage(imageComponent);
background.setImageScaleMode(Background.SCALE_FIT_ALL);
background.setCapability(Background.ALLOW_IMAGE_WRITE);
background.setApplicationBounds(new BoundingSphere());
return background;
}
private TransformGroup createSomeCube()
{
ColorCube cube = new ColorCube(0.5f);
Transform3D t = new Transform3D();
t.rotY(0.2);
t.setScale(0.1);
TransformGroup tg = new TransformGroup();
tg.setTransform(t);
tg.removeAllChildren();
tg.addChild(cube);
return tg;
}
}
So I have an animated gif that I load into an ImageIcon like this:
Image image = new ImageIcon("image.gif").getImage();
and I can draw it using this:
g.drawImage(image, x, y, null);
I know that I can mirror it on the fly using AffineTransform, but I need to be able to mirror it horizontally after loading, so that I can draw the mirrored one instead if needed without the overhead of transforming it every time it gets redrawn. Is there a way to do this using swing/awt?
A library that could do this would also be a huge help.
The problem is, as you have pointed out, is the fact the gif's are animated.
Unless you desperately want to take over the job of having to render each frame yourself, the only choice you have is to use an AffineTransform with in the paint method.
Generally speaking, you shouldn't see a significant difference (in rendering).
If you are really desperate, you could simply pre-render the gif externally and provide a mirrored version
Updated with a "kind of" working example
This is a combination of this and this answers, using this GIF writer.
Basically what this example does is it reads an original gif image, mirrors it frame by frame, and writes back out to a mirrored file.
It then loads both the original and mirrored files back in as ImageIcons, mostly because I'm not really up to re-inventing the wheel for display animated gifs. Yes, you could do it and everything you would need is provided within..
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("java_animated.gif"), new File("Mirror.gif"));
orig = new ImageIcon("java_animated.gif");
mirror = new ImageIcon("Mirror.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));
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");
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);
}
}
}
Caveats
The Gif writer currently only works with fixed rate gifs. It should be possible to change this, but I didn't have the time.
Basically, as I understand it, you would need to pass a "frame" delay to the writeToSquence method. Within this method you would need to construct an appropriate IIOMetadata with all the required properties, plus your frame delay...
Updated after playing with original gif
The GIF I was playing with was optimised. That is, each frame "added" to the animation, rather then being a brand new frame. Yours is the other way round. Each frame is an entire image.
Now, there are probably lots of ways you could check for this, but right now, I can't be bothered...
Instead...in the mirror(File, File) method, I changed it so that rather then using a single "master" image, each frame creates a new BufferedImage
BufferedImage frame = new BufferedImage(imageAttr.get("imageWidth"), imageAttr.get("imageHeight"), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = frame.createGraphics();
g2d.drawImage(image, imageAttr.get("imageLeftPosition"), imageAttr.get("imageTopPosition"), null);
g2d.dispose();
frame = mirror(frame);
ImageIO.write(frame, "png", new File("img" + i + ".png"));
images.add(frame);
I also updated the GifSequenceWriter to set the meta data to more closely match the original as well...
graphicsControlExtensionNode.setAttribute("disposalMethod", "restoreToBackgroundColor");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute(
"transparentColorFlag",
"TRUE");
..overhead of transforming it every time..
That overhead is just about 0. But if you don't want to use AffineTransform simply change the x,y in a loop.
See also Show an animated BG in Swing for more tips.
Note
This:
g.drawImage(image, x, y, null);
Should be:
g.drawImage(image, x, y, this); // containers are typically an ImageObserver!
For non-animated images, you can create a mirrored image.
// Width and height
int w = image.getWidth(null);
int h = image.getHeight(null);
// Create a new BufferedImage
BufferedImage mirror = new BufferedImage(w, h);
// Draw the image flipping it horizontally
Graphics2D g = mirror.createGraphics();
g.drawImage(image, 0, 0, w, h, w, 0, 0, h, null);
// Dispose the graphics.
g.dispose();
You can then use mirror which is already mirrored horizontally.
For my application I need to dynamically create thumbnails of websites. So far I have this code from SO:
public class CreateWebsiteThumbnail {
private static final int WIDTH = 128;
private static final int HEIGHT = 128;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
public void capture(Component component) {
component.setSize(image.getWidth(), image.getHeight());
Graphics2D g = image.createGraphics();
try {
component.paint(g);
} finally {
g.dispose();
}
}
private BufferedImage getScaledImage(int width, int height) {
BufferedImage buffer = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffer.createGraphics();
try {
g.drawImage(image, 0, 0, width, height, null);
} finally {
g.dispose();
}
return buffer;
}
public void save(File png, int width, int height) throws IOException {
ImageIO.write(getScaledImage(width, height), "png", png);
}
public static void main(String[] args) throws IOException {
Shell shell = new Shell();
Browser browser = new Browser(shell, SWT.EMBEDDED);
browser.setUrl("http://www.google.com");
CreateWebsiteThumbnail cap = new CreateWebsiteThumbnail();
cap.capture(What her?);
cap.save(new File("foo.png"), 64, 64);
}
}
But as you can see here, I don't know which part of the browser I should pass to my capture method. Any hints?
I don't see, how the code you provided could work. The Shell is not opened, it has no size, the Browser didn't get time to actually load anything, no event loop seems to be running to enable any drawing, ...
The following code does a screenshot of a page using SWT browser:
import java.io.IOException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class CreateWebsiteThumbnail {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
public static void main( String[] args ) throws IOException {
final Display display = new Display();
final Shell shell = new Shell();
shell.setLayout(new FillLayout());
final Browser browser = new Browser(shell, SWT.EMBEDDED);
browser.addProgressListener(new ProgressListener() {
#Override
public void changed( ProgressEvent event ) {}
#Override
public void completed( ProgressEvent event ) {
shell.forceActive();
display.asyncExec(new Runnable() {
#Override
public void run() {
grab(display, shell, browser);
}
});
}
});
browser.setUrl("http://www.google.com");
shell.setSize(WIDTH, HEIGHT);
shell.open();
while ( !shell.isDisposed() ) {
if ( !display.readAndDispatch() ) display.sleep();
}
display.dispose();
}
private static void grab( final Display display, final Shell shell, final Browser browser ) {
final Image image = new Image(display, browser.getBounds());
GC gc = new GC(browser);
gc.copyArea(image, 0, 0);
gc.dispose();
ImageLoader loader = new ImageLoader();
loader.data = new ImageData[] { image.getImageData() };
loader.save("foo.png", SWT.IMAGE_PNG);
image.dispose();
shell.dispose();
}
}
But there are some serious caveats:
You cannot do this off-screen. SWT screenshots are just a copy of the current Display.
The window containing your browser must be on top, when taking the screenshot.
The page should be visible after onLoad (which is actually not the case with google.com, but works for me because of the asyncExec call anyway - if you get a white image, try another URL)
The result is dependant on your OS and its installed browsers
I'd go with a non Java-solution, in order to get off screen drawing. I believe the linked question might help you to get further.
As far as I know, when you use a Browser object, the webpage you load is rendered directly on the Composite object you pass to it through the constructor. In your case, it is rendered on your Shell item which is a window-style object. There is no method to render the webpage directly on, say, an Image object.
You can try, though, to instantiate your Browser on a Canvas object and save the image directly from there.
Unfortunately I am unable to test whether this works or not because I have neither Eclipse nor SWT installed; I am pretty sure though that, if what you want to do is doable, this is the only way.
I did some experimenting myself. My intuition was to try on JEditorPane just like mentioned in the answer your code is based on. I do not know of how much help it is going to be but it might help. I gives some results, but from obvious reasons, luck of css support etc in JEditorPane it all looks ugly.
This is my code:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
public class WebPageToImageThumbnail {
public static void main(String[] a) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JEditorPane editorPane = new JEditorPane();
editorPane.setEditorKit(new HTMLEditorKit());
try {
editorPane.setPage(new URL("http://weblogs.java.net/blog/alex2d/archive/2008/12/jwebpane_projec.html"));
} catch (IOException ex) {
}
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(editorPane), BorderLayout.CENTER);
panel.add(new JButton(new AbstractAction("SAVE") {
#Override
public void actionPerformed(ActionEvent e) {
BufferedImage image = capture(editorPane);
try {
save(image, new File("foo.png"), 64, 64);
} catch (IOException ex) {
}
}
}), BorderLayout.SOUTH);
frame.setContentPane(panel);
frame.setSize(600, 400);
frame.setVisible(true);
}
});
}
public static BufferedImage capture(Component component) {
BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB);//component.setSize(image.getWidth(), image.getHeight());
Graphics2D g = image.createGraphics();
try {
component.paint(g);
} finally {
g.dispose();
}
return image;
}
private static BufferedImage getScaledImage(BufferedImage image, int width, int height) {
BufferedImage buffer = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffer.createGraphics();
try {
g.drawImage(image, 0, 0, width, height, null);
} finally {
g.dispose();
}
return buffer;
}
public static void save(BufferedImage image, File png, int width, int height) throws IOException {
ImageIO.write(getScaledImage(image, width, height), "png", png);
}
}
I also did some digging about the SWT I found some thinks that might be of use, but since I currently luck time, I cannot test. From what I read I have to agree with #Emanuele Bezzi (+1) that we are going to have to use the Shell somehow to get the content of the site, in which we are effectively only interested.
I found out that Shell has print method which takes GC object which can paint including to Image and other interesting to us stuff, the documentation says: "Class GC is where all of the drawing capabilities that are supported by SWT are located. Instances are used to draw on either an Image, a Control, or directly on a Display.".
Yet at this particular moment it is not clear to me how to exactly get it to do what I want. Anyway I am raising this point just to make you aware. I still need to look into it further.
Maybe you're better off rendering the page offscreen right from the start. For such a task you may for example use "flying saucer" (which is xhtml only, so you need to tidy up). There seem to be also some tools to interface Webkit directly from Java.