/
CanvasFrame.java
447 lines (414 loc) · 16.6 KB
/
CanvasFrame.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/*
* Copyright (C) 2009,2010,2011,2012,2013 Samuel Audet
*
* This file is part of JavaCV.
*
* JavaCV is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version (subject to the "Classpath" exception
* as provided in the LICENSE.txt file that accompanied this code).
*
* JavaCV is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JavaCV. If not, see <http://www.gnu.org/licenses/>.
*/
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;
import static org.bytedeco.javacpp.opencv_core.*;
/**
*
* @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 {
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 {
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;
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(CvScalar color) {
showColor(new Color((int)color.red(), (int)color.green(), (int)color.blue()));
}
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(AbstractArray image) {
showImage(image, false);
}
public void showImage(AbstractArray image, boolean flipChannels) {
showImage(image.getBufferedImage(image.getBufferedImageType() ==
BufferedImage.TYPE_CUSTOM ? 1.0 : inverseGamma, flipChannels));
}
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;
}
}
}
}