/*
* Copyright (C) 2009-2015 Samuel Audet
*
* Licensed either under the Apache License, Version 2.0, or (at your option)
* under the terms of the GNU General Public License as published by
* the Free Software Foundation (subject to the "Classpath" exception),
* either version 2, or any later version (collectively, the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.gnu.org/licenses/
* http://www.gnu.org/software/classpath/license.html
*
* or as provided in the LICENSE.txt file that accompanied this code.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bytedeco.javacv;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_ProfileRGB;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JRootPane;
/**
*
* @author Samuel Audet
*
* Make sure OpenGL or XRender is enabled to get low latency, something like
* export _JAVA_OPTIONS=-Dsun.java2d.opengl=True
* export _JAVA_OPTIONS=-Dsun.java2d.xrender=True
*/
public class CanvasFrame extends JFrame {
public static class Exception extends java.lang.Exception {
public Exception(String message) { super(message); }
public Exception(String message, Throwable cause) { super(message, cause); }
}
public static String[] getScreenDescriptions() {
GraphicsDevice[] screens = getScreenDevices();
String[] descriptions = new String[screens.length];
for (int i = 0; i < screens.length; i++) {
descriptions[i] = screens[i].getIDstring();
}
return descriptions;
}
public static DisplayMode getDisplayMode(int screenNumber) {
GraphicsDevice[] screens = getScreenDevices();
if (screenNumber >= 0 && screenNumber < screens.length) {
return screens[screenNumber].getDisplayMode();
} else {
return null;
}
}
public static double getGamma(int screenNumber) {
GraphicsDevice[] screens = getScreenDevices();
if (screenNumber >= 0 && screenNumber < screens.length) {
return getGamma(screens[screenNumber]);
} else {
return 0.0;
}
}
public static double getDefaultGamma() {
return getGamma(getDefaultScreenDevice());
}
public static double getGamma(GraphicsDevice screen) {
ColorSpace cs = screen.getDefaultConfiguration().getColorModel().getColorSpace();
if (cs.isCS_sRGB()) {
return 2.2;
} else {
try {
return ((ICC_ProfileRGB)((ICC_ColorSpace)cs).getProfile()).getGamma(0);
} catch (RuntimeException e) { }
}
return 0.0;
}
public static GraphicsDevice getScreenDevice(int screenNumber) throws Exception {
GraphicsDevice[] screens = getScreenDevices();
if (screenNumber >= screens.length) {
throw new Exception("CanvasFrame Error: Screen number " + screenNumber + " not found. " +
"There are only " + screens.length + " screens.");
}
return screens[screenNumber];//.getDefaultConfiguration();
}
public static GraphicsDevice[] getScreenDevices() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
}
public static GraphicsDevice getDefaultScreenDevice() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
}
public CanvasFrame(String title) {
this(title, 0.0);
}
public CanvasFrame(String title, double gamma) {
super(title);
init(false, null, gamma);
}
public CanvasFrame(String title, GraphicsConfiguration gc) {
this(title, gc, 0.0);
}
public CanvasFrame(String title, GraphicsConfiguration gc, double gamma) {
super(title, gc);
init(false, null, gamma);
}
public CanvasFrame(String title, int screenNumber, DisplayMode displayMode) throws Exception {
this(title, screenNumber, displayMode, 0.0);
}
public CanvasFrame(String title, int screenNumber, DisplayMode displayMode, double gamma) throws Exception {
super(title, getScreenDevice(screenNumber).getDefaultConfiguration());
init(true, displayMode, gamma);
}
private void init(final boolean fullScreen, final DisplayMode displayMode, final double gamma) {
Runnable r = new Runnable() { public void run() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addKeyEventDispatcher(keyEventDispatch);
GraphicsDevice gd = getGraphicsConfiguration().getDevice();
DisplayMode d = gd.getDisplayMode(), d2 = null;
if (displayMode != null && d != null) {
int w = displayMode.getWidth();
int h = displayMode.getHeight();
int b = displayMode.getBitDepth();
int r = displayMode.getRefreshRate();
d2 = new DisplayMode(w > 0 ? w : d.getWidth(), h > 0 ? h : d.getHeight(),
b > 0 ? b : d.getBitDepth(), r > 0 ? r : d.getRefreshRate());
}
if (fullScreen) {
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
setResizable(false);
gd.setFullScreenWindow(CanvasFrame.this);
} else {
setLocationByPlatform(true);
}
if (d2 != null && !d2.equals(d)) {
gd.setDisplayMode(d2);
}
double g = gamma == 0.0 ? getGamma(gd) : gamma;
inverseGamma = g == 0.0 ? 1.0 : 1.0/g;
// Must be called after the fullscreen stuff, but before
// getting our BufferStrategy or even creating our Canvas
setVisible(true);
initCanvas(fullScreen, displayMode, gamma);
}};
if (EventQueue.isDispatchThread()) {
r.run();
} else {
try {
EventQueue.invokeAndWait(r);
} catch (java.lang.Exception ex) { }
}
}
protected void initCanvas(boolean fullScreen, DisplayMode displayMode, double gamma) {
canvas = new Canvas() {
@Override public void update(Graphics g) {
paint(g);
}
@Override public void paint(Graphics g) {
// Calling BufferStrategy.show() here sometimes throws
// NullPointerException or IllegalStateException,
// but otherwise seems to work fine.
try {
if (canvas.getWidth() <= 0 || canvas.getHeight() <= 0) {
return;
}
BufferStrategy strategy = canvas.getBufferStrategy();
do {
do {
g = strategy.getDrawGraphics();
if (color != null) {
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
}
if (image != null) {
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
if (buffer != null) {
g.drawImage(buffer, 0, 0, getWidth(), getHeight(), null);
}
g.dispose();
} while (strategy.contentsRestored());
strategy.show();
} while (strategy.contentsLost());
} catch (NullPointerException e) {
} catch (IllegalStateException e) { }
}
};
if (fullScreen) {
canvas.setSize(getSize());
needInitialResize = false;
} else {
canvas.setSize(10,10); // mac bug
needInitialResize = true;
}
getContentPane().add(canvas);
canvas.setVisible(true);
canvas.createBufferStrategy(2);
//canvas.setIgnoreRepaint(true);
}
// used for example as debugging console...
public static CanvasFrame global = null;
// Latency is about 60 ms on Metacity and Windows XP, and 90 ms on Compiz Fusion,
// but we set the default to twice as much to take into account the roundtrip
// camera latency as well, just to be sure
public static final long DEFAULT_LATENCY = 200;
private long latency = DEFAULT_LATENCY;
private KeyEvent keyEvent = null;
private KeyEventDispatcher keyEventDispatch = new KeyEventDispatcher() {
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
synchronized (CanvasFrame.this) {
keyEvent = e;
CanvasFrame.this.notify();
}
}
return false;
}
};
protected Canvas canvas = null;
protected boolean needInitialResize = false;
protected double initialScale = 1.0;
protected double inverseGamma = 1.0;
private Color color = null;
private Image image = null;
private BufferedImage buffer = null;
private Java2DFrameConverter converter = new Java2DFrameConverter();
public long getLatency() {
// if there exists some way to estimate the latency in real time,
// add it here
return latency;
}
public void setLatency(long latency) {
this.latency = latency;
}
public void waitLatency() throws InterruptedException {
Thread.sleep(getLatency());
}
public KeyEvent waitKey() throws InterruptedException {
return waitKey(0);
}
public synchronized KeyEvent waitKey(int delay) throws InterruptedException {
if (delay >= 0) {
keyEvent = null;
wait(delay);
}
KeyEvent e = keyEvent;
keyEvent = null;
return e;
}
public Canvas getCanvas() {
return canvas;
}
public Dimension getCanvasSize() {
return canvas.getSize();
}
public void setCanvasSize(final int width, final int height) {
Dimension d = getCanvasSize();
if (d.width == width && d.height == height) {
return;
}
Runnable r = new Runnable() { public void run() {
// There is apparently a bug in Java code for Linux, and what happens goes like this:
// 1. Canvas gets resized, checks the visible area (has not changed) and updates
// BufferStrategy with the same size. 2. pack() resizes the frame and changes
// the visible area 3. We call Canvas.setSize() with different dimensions, to make
// it check the visible area and reallocate the BufferStrategy almost correctly
// 4. Finally, we resize the Canvas to the desired size... phew!
setExtendedState(NORMAL); // force unmaximization
canvas.setSize(width, height);
pack();
canvas.setSize(width+1, height+1);
canvas.setSize(width, height);
needInitialResize = false;
}};
if (EventQueue.isDispatchThread()) {
r.run();
} else {
try {
EventQueue.invokeAndWait(r);
} catch (java.lang.Exception ex) { }
}
}
public double getCanvasScale() {
return initialScale;
}
public void setCanvasScale(double initialScale) {
this.initialScale = initialScale;
this.needInitialResize = true;
}
public Graphics2D createGraphics() {
if (buffer == null || buffer.getWidth() != canvas.getWidth() || buffer.getHeight() != canvas.getHeight()) {
BufferedImage newbuffer = canvas.getGraphicsConfiguration().createCompatibleImage(
canvas.getWidth(), canvas.getHeight(), Transparency.TRANSLUCENT);
if (buffer != null) {
Graphics g = newbuffer.getGraphics();
g.drawImage(buffer, 0, 0, null);
g.dispose();
}
buffer = newbuffer;
}
return buffer.createGraphics();
}
public void releaseGraphics(Graphics2D g) {
g.dispose();
canvas.paint(null);
}
public void showColor(Color color) {
this.color = color;
this.image = null;
canvas.paint(null);
}
// Java2D will do gamma correction for TYPE_CUSTOM BufferedImage, but
// not for the standard types, so we need to do it manually.
public void showImage(Frame image) {
showImage(image, false);
}
public void showImage(Frame image, boolean flipChannels) {
showImage(converter.getBufferedImage(image, converter.getBufferedImageType(image) ==
BufferedImage.TYPE_CUSTOM ? 1.0 : inverseGamma, flipChannels, null));
}
public void showImage(Image image) {
if (image == null) {
return;
} else if (isResizable() && needInitialResize) {
int w = (int)Math.round(image.getWidth (null)*initialScale);
int h = (int)Math.round(image.getHeight(null)*initialScale);
setCanvasSize(w, h);
}
this.color = null;
this.image = image;
canvas.paint(null);
}
// This should not be called from the event dispatch thread (EDT),
// but if it is, it should not totally crash... In the worst case,
// it will simply timeout waiting for the moved events.
public static void tile(final CanvasFrame[] frames) {
class MovedListener extends ComponentAdapter {
boolean moved = false;
@Override public void componentMoved(ComponentEvent e) {
moved = true;
Component c = e.getComponent();
synchronized (c) {
c.notify();
}
}
}
final MovedListener movedListener = new MovedListener();
// layout the canvas frames for the cameras in tiles
int canvasCols = (int)Math.round(Math.sqrt(frames.length));
if (canvasCols*canvasCols < frames.length) {
// if we don't get a square, favor horizontal layouts
// since screens are usually wider than cameras...
// and we also have title bars, tasks bar, menus, etc that
// takes up vertical space
canvasCols++;
}
int canvasX = 0, canvasY = 0;
int canvasMaxY = 0;
for (int i = 0; i < frames.length; i++) {
final int n = i;
final int x = canvasX;
final int y = canvasY;
try {
movedListener.moved = false;
EventQueue.invokeLater(new Runnable() {
public void run() {
frames[n].addComponentListener(movedListener);
frames[n].setLocation(x, y);
}
});
int count = 0;
while (!movedListener.moved && count < 5) {
// wait until the window manager actually places our window...
// wait a maximum of 500 ms since this does not work if
// we are on the event dispatch thread. also some window
// managers like Windows do not always send us the event...
synchronized (frames[n]) {
frames[n].wait(100);
}
count++;
}
EventQueue.invokeLater(new Runnable() {
public void run() {
frames[n].removeComponentListener(movedListener);
}
});
} catch (java.lang.Exception ex) { }
canvasX = frames[i].getX()+frames[i].getWidth();
canvasMaxY = Math.max(canvasMaxY, frames[i].getY()+frames[i].getHeight());
if ((i+1)%canvasCols == 0) {
canvasX = 0;
canvasY = canvasMaxY;
}
}
}
}