source: trunk/src/org/expeditee/gio/javafx/JavaFXGraphicsManager.java@ 1545

Last change on this file since 1545 was 1545, checked in by bnemhaus, 3 years ago

Expeditee now respects the users antialiasing setting. The previous system

  1. Attempted to respect antialiasing by checking the setting and enabling it if the setting was true
  2. In the process of painting the frame some special items (link marks etc) want to use antialiasing reguardless of setting. This was achieved by turning antialiasing on, doing the thing and then turning it off. This unfortunately meant that, in the scenario that the users antialiasing setting is on, it gets turned off after drawing one of these special items and doesn't get turn on again until all items have been drawn.

The new system still always turns antialiasing on for these special items, but instead of turning it off after, it returns it to the setting it was previously on. This is achieved with two new functions: setTransientAntialiasingOn() and setTransientAntialiasingOff().

On a related note, there was also an issue with some polygons being drawn with a thickness of 0.0f, which was causing them to antialias badly. They now have a thickness of 1.0f.

File size: 19.1 KB
Line 
1package org.expeditee.gio.javafx;
2
3import java.util.HashSet;
4import java.util.Set;
5import java.util.Stack;
6
7import org.expeditee.Util;
8import org.expeditee.core.Clip;
9import org.expeditee.core.Colour;
10import org.expeditee.core.Cursor;
11import org.expeditee.core.Dimension;
12import org.expeditee.core.Fill;
13import org.expeditee.core.Font;
14import org.expeditee.core.GradientFill;
15import org.expeditee.core.Image;
16import org.expeditee.core.Point;
17import org.expeditee.core.Stroke;
18import org.expeditee.core.TextLayout;
19import org.expeditee.core.bounds.AxisAlignedBoxBounds;
20import org.expeditee.core.bounds.PolygonBounds;
21import org.expeditee.gio.GraphicsManager;
22import org.expeditee.gio.GraphicsSurfaceStack;
23
24import javafx.application.Application;
25import javafx.application.Platform;
26import javafx.geometry.Bounds;
27import javafx.scene.Group;
28import javafx.scene.Scene;
29import javafx.scene.SnapshotParameters;
30import javafx.scene.canvas.Canvas;
31import javafx.scene.canvas.GraphicsContext;
32import javafx.scene.effect.BlendMode;
33import javafx.scene.input.KeyCombination;
34import javafx.scene.paint.Color;
35import javafx.stage.Screen;
36import javafx.stage.Stage;
37
38public class JavaFXGraphicsManager extends GraphicsManager {
39
40 /** Singleton instance. */
41 private static JavaFXGraphicsManager _instance;
42
43 /** Singleton instantiator. */
44 public static JavaFXGraphicsManager getInstance()
45 {
46 if (_instance == null) _instance = new JavaFXGraphicsManager();
47
48 return _instance;
49 }
50
51 /** The largest size a window icon can be. */
52 private static final int MAX_ICON_SIZE = 256;
53 /** The smallest size a window icon can be. */
54 private static final int MIN_ICON_SIZE = 8;
55
56 /** The startup thread for the JavaFX application. */
57 private static Thread _jfxAppThread;
58
59 /** The stack of drawing surfaces. */
60 private GraphicsSurfaceStack<Canvas> _surfaceStack;
61
62 /** A stack for surface clips to aid in clip clearing. */
63 private Clip _currentClip;
64 private Stack<Clip> _clipStack;
65
66 private JavaFXGraphicsManager()
67 {
68 // Create the clip stack
69 _currentClip = null;
70 _clipStack = new Stack<Clip>();
71
72 // Set the ExpediteeApplication manager
73 ExpediteeApplication.manager = this;
74
75 // Start up the JavaFX thread on another thread as it blocks until program exit
76 _jfxAppThread = new Thread("_jfxAppThread") {
77 @Override
78 public void run()
79 {
80 Application.launch(ExpediteeApplication.class);
81 }
82 };
83
84 // Block the main thread until the JavaFX thread is ready
85 synchronized(_jfxAppThread) {
86 _jfxAppThread.start();
87 try {
88 _jfxAppThread.wait();
89 } catch (InterruptedException e) {
90 System.err.println("Error while initialising GraphicsManager. Aborting...");
91 e.printStackTrace(System.err);
92 System.exit(1);
93 }
94 }
95
96 // Create the surface stack
97 _surfaceStack = new GraphicsSurfaceStack<Canvas>() {
98 @Override
99 public Canvas getSurfaceFromImage(Image image)
100 {
101 Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
102 canvas.getGraphicsContext2D().drawImage(JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(image), 0, 0);
103 return canvas;
104 }
105
106 @Override
107 public void setSurfaceClip(Canvas surface, Clip clip)
108 {
109 if (surface == null) return;
110
111 if (clip != null && clip.isFullyClipped()) return;
112
113 GraphicsContext g = surface.getGraphicsContext2D();
114
115 // Restore the pre-clipped state
116 g.restore();
117
118 // If we are clearing the clip, we are done
119 if (clip == null || clip.getBounds() == null) return;
120
121 // Re-save the pre-clipped state
122 g.save();
123
124 // Set the clip
125 g.beginPath();
126 AxisAlignedBoxBounds clipBounds = clip.getBounds();
127 g.rect(clipBounds.getMinX(), clipBounds.getMinY(), clipBounds.getWidth(), clipBounds.getHeight());
128 g.closePath();
129 g.clip();
130 }
131 };
132 _surfaceStack.setRootSurface(ExpediteeApplication.theCanvas);
133
134 // Disable JavaFX's fullscreen exiting system
135 ExpediteeApplication.theStage.setFullScreenExitHint("");
136 ExpediteeApplication.theStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
137
138 // Block until the window is ready
139 JavaFXMiscManager.waitUntilPropertyEquals(ExpediteeApplication.theStage.showingProperty(), true);
140
141 }
142
143 @Override
144 protected GraphicsSurfaceStack<?> getGraphicsSurfaceStack()
145 {
146 return _surfaceStack;
147 }
148
149 @Override
150 public Dimension getScreenSize()
151 {
152 return JavaFXConversions.fromJavaFXRectangle2D(Screen.getPrimary().getBounds()).getSize();
153 }
154
155 @Override
156 public Point getWindowLocation()
157 {
158 return translateCanvasToScreen(new Point(0, 0));
159 }
160
161 @Override
162 public void setWindowLocation(Point p)
163 {
164 if (p == null) return;
165
166 Point currentCanvasPos = getWindowLocation();
167 Point currentStagePos = new Point((int) ExpediteeApplication.theStage.getX(), (int) ExpediteeApplication.theStage.getY());
168
169 Point delta = Point.difference(currentStagePos, currentCanvasPos);
170
171 Point newStagePos = p.clone().add(delta);
172
173 ExpediteeApplication.theStage.setX(newStagePos.getX());
174 ExpediteeApplication.theStage.setY(newStagePos.getY());
175 }
176
177 @Override
178 public Dimension getWindowSize()
179 {
180 return new Dimension((int) ExpediteeApplication.theScene.getWidth(), (int) ExpediteeApplication.theScene.getHeight());
181 }
182
183 @Override
184 public void setWindowSize(Dimension d)
185 {
186 if (d == null) return;
187
188 Dimension decorationDimensions = getDecorationDimensions();
189
190 ExpediteeApplication.theStage.setWidth(d.width + decorationDimensions.width);
191 ExpediteeApplication.theStage.setHeight(d.height + decorationDimensions.height);
192 ExpediteeApplication.theCanvas.setWidth(d.width);
193 ExpediteeApplication.theCanvas.setHeight(d.height);
194
195 }
196
197 @Override
198 public void requestFocus()
199 {
200 ExpediteeApplication.theStage.requestFocus();
201 }
202
203 @Override
204 public void setWindowIcon(Image image)
205 {
206 // TODO: Work out why this doesn't work. cts16
207 JavaFXImageManager imageManager = JavaFXMiscManager.getIfUsingJavaFXImageManager();
208
209 if (imageManager == null || !imageManager.isImageValid(image)) return;
210
211 ExpediteeApplication.theStage.getIcons().clear();
212
213 ExpediteeApplication.theStage.getIcons().add(imageManager.getInternalImage(image));
214
215 /*
216 Set<Image> iconSet = createIconSet(image);
217
218 if (iconSet != null) {
219 for (Image icon : iconSet) ExpediteeApplication.theStage.getIcons().add(imageManager.getInternalImage(icon));
220 }
221 */
222 }
223
224 @Override
225 public void setWindowTitle(String title)
226 {
227 if (title == null) return;
228
229 ExpediteeApplication.theStage.setTitle(title);
230 }
231
232 private Point _preFullscreenWindowLocation = null;
233 private Dimension _preFullscreenWindowSize = null;
234
235 @Override
236 public boolean canGoFullscreen()
237 {
238 // TODO: Check if this is always true. cts16
239 return true;
240 }
241
242 @Override
243 public boolean isFullscreen()
244 {
245 return ExpediteeApplication.theStage.isFullScreen();
246 }
247
248 @Override
249 public void goFullscreen()
250 {
251 _preFullscreenWindowLocation = getWindowLocation();
252 _preFullscreenWindowSize = getWindowSize();
253 ExpediteeApplication.theStage.setFullScreen(true);
254 }
255
256 @Override
257 public void exitFullscreen()
258 {
259 ExpediteeApplication.theStage.setFullScreen(false);
260 setWindowLocation(_preFullscreenWindowLocation);
261 setWindowSize(_preFullscreenWindowSize);
262 }
263
264 @Override
265 public void pushDrawingSurface(Image image)
266 {
267 _surfaceStack.push(image);
268 _clipStack.push(_currentClip);
269 _currentClip = null;
270 }
271
272 @Override
273 public Image popDrawingSurface()
274 {
275 // Get the current canvas
276 Canvas canvas = _surfaceStack.getCurrentSurface();
277
278 // Get its corresponding image
279 Image image = _surfaceStack.pop();
280
281 // Check we haven't hit the bottom of the stack
282 if (image == null) return null;
283
284 // Write the canvas contents back to the image
285 SnapshotParameters params = new SnapshotParameters();
286 params.setFill(Color.TRANSPARENT);
287 canvas.snapshot(params, JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(image));
288
289 // Reload the next surface's clip
290 _currentClip = _clipStack.pop();
291
292 return image;
293 }
294
295 @Override
296 public void setCompositeAlpha(float alpha)
297 {
298 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
299
300 // Make sure we are setting to the base of the stack
301 g.restore();
302
303 // Set the blend alpha
304 g.setGlobalBlendMode(BlendMode.SRC_OVER);
305 g.setGlobalAlpha(alpha);
306
307 // Re-clip
308 _surfaceStack.ensureClip();
309 }
310
311 @Override
312 public void setAntialiasing(boolean on)
313 {
314 // TODO: Implement. cts16
315 }
316
317 public void setTransientAntialiasingOn() {
318 // TODO: Implement. Bryce
319 }
320
321 public void setTransientAntialiasingOff() {
322 // TODO: Implement. Bryce
323 }
324
325 @Override
326 public void setFont(Font font)
327 {
328 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
329
330 g.restore();
331
332 g.setFont(JavaFXMiscManager.getIfUsingJavaFXFontManager().getInternalFont(font));
333
334 // Re-clip
335 _surfaceStack.ensureClip();
336 }
337
338 @Override
339 public void drawImage(Image image, Point topLeft, Dimension size, double angle, Point cropTopLeft, Dimension cropSize)
340 {
341 // Can't draw nothing
342 if (image == null) return;
343
344 // Can't draw nowhere
345 if (topLeft == null) return;
346
347 // If the cropped area is degenerate, abort
348 if (cropSize != null && (cropSize.width <= 0 || cropSize.height <= 0)) return;
349
350 // If the crop area is the entire image, pretend we are not cropping
351 if (Point.ORIGIN.equals(cropTopLeft) && image.getSize().equals(cropSize)) {
352 cropTopLeft = null;
353 cropSize = null;
354 }
355
356 JavaFXImageManager manager = JavaFXMiscManager.getIfUsingJavaFXImageManager();
357
358 // Default is the entire image
359 Image croppedImage = image;
360
361 // If we are cropping, do this now into a temporary image
362 if (cropTopLeft != null && cropSize != null) {
363 croppedImage = manager.createImage(cropSize.width, cropSize.height);
364 manager.getInternalImage(croppedImage).getPixelWriter().setPixels(0, 0, cropSize.width, cropSize.height, manager.getInternalImage(image).getPixelReader(), cropTopLeft.getX(), cropTopLeft.getY());
365 }
366
367 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
368
369 g.save();
370 if (size != null) g.scale(((double) size.width) / croppedImage.getWidth(), ((double) size.height) / croppedImage.getHeight());
371 if (angle != 0.0) g.rotate(angle);
372 if (topLeft != null) g.translate(topLeft.getX(), topLeft.getY());
373 g.drawImage(JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(croppedImage), 0, 0);
374 g.restore();
375 }
376
377 @Override
378 public void drawRectangle(Point topLeft, Dimension size, double angle, Fill fill, Colour borderColour, Stroke borderStroke, Dimension cornerRadius)
379 {
380 // Can't draw nowhere
381 if (topLeft == null) return;
382
383 // Can't draw nothing
384 if (size == null) return;
385
386 // If not filled and has no border, why bother?
387 if (fill == null && (borderColour == null || borderStroke == null)) return;
388
389 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
390
391 g.save();
392
393 Point centre = new Point(topLeft.getX() + size.width / 2, topLeft.getY() + size.height / 2);
394 g.translate(-centre.getX(), -centre.getY());
395 if (angle != 0.0) g.rotate(angle);
396 g.translate(centre.getX(), centre.getY());
397
398 if (fill != null) {
399 if (fill instanceof GradientFill) {
400 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
401 } else {
402 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
403 }
404
405 if (cornerRadius == null) {
406 g.fillRect(topLeft.getX(), topLeft.getY(), size.width, size.height);
407 } else {
408 g.fillRoundRect(topLeft.getX(), topLeft.getY(), size.width, size.height, cornerRadius.width, cornerRadius.height);
409 }
410 }
411
412 if (borderColour != null && borderStroke != null) {
413 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
414 setStroke(g, borderStroke);
415
416 if (cornerRadius == null) {
417 g.strokeRect(topLeft.getX(), topLeft.getY(), size.width, size.height);
418 } else {
419 g.strokeRoundRect(topLeft.getX(), topLeft.getY(), size.width, size.height, cornerRadius.width, cornerRadius.height);
420 }
421 }
422
423 g.restore();
424 }
425
426 @Override
427 public void drawOval(Point centre, Dimension diameters, double angle, Fill fill, Colour borderColour, Stroke borderStroke)
428 {
429 // Can't draw nowhere
430 if (centre == null) return;
431
432 // Can't draw nothing
433 if (diameters == null) return;
434
435 // If not filled and has no border, why bother?
436 if (fill == null && (borderColour == null || borderStroke == null)) return;
437
438 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
439
440 g.save();
441
442 if (angle != 0.0) {
443 g.translate(-centre.getX(), -centre.getY());
444 g.rotate(angle);
445 g.translate(centre.getX(), centre.getY());
446 }
447
448 if (fill != null) {
449 if (fill instanceof GradientFill) {
450 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
451 } else {
452 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
453 }
454
455 g.fillOval(centre.getX() - diameters.width / 2, centre.getY() - diameters.height / 2, diameters.width, diameters.height);
456 }
457
458 if (borderColour != null && borderStroke != null) {
459 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
460 setStroke(g, borderStroke);
461
462 g.strokeOval(centre.getX() - diameters.width / 2, centre.getY() - diameters.height / 2, diameters.width, diameters.height);
463 }
464
465 g.restore();
466 }
467
468 @Override
469 public void drawPolygon(PolygonBounds points, Point offset, Dimension scale, double angle, Fill fill, Colour borderColour, Stroke borderStroke)
470 {
471 if (points == null || points.getPointCount() < 2) return;
472 if (fill == null && (borderColour == null || borderStroke == null)) return;
473
474 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
475
476 g.save();
477
478 Point centre = points.getCentre();
479 g.translate(-centre.getX(), -centre.getY());
480 if (angle != 0.0) g.rotate(angle);
481 if (scale != null) {
482 double xScale = ((double) scale.width) / (points.getMaxX() - points.getMinX());
483 double yScale = ((double) scale.height) / (points.getMaxY() - points.getMinY());
484 g.scale(xScale, yScale);
485 }
486 g.translate(centre.getX(), centre.getY());
487 if (offset != null) g.translate(offset.getX(), offset.getY());
488
489 Point[] pointArray = points.toArray();
490 int nPoints = pointArray.length;
491 double[] xArray = new double[nPoints];
492 double[] yArray = new double[nPoints];
493
494 for (int i = 0; i < nPoints; i++) {
495 xArray[i] = pointArray[i].getX() + 0.5;
496 yArray[i] = pointArray[i].getY() + 0.5;
497 }
498
499 if (fill != null) {
500 if (fill instanceof GradientFill) {
501 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
502 } else {
503 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
504 }
505
506 g.fillPolygon(xArray, yArray, nPoints);
507 }
508
509 if (borderColour != null && borderStroke != null) {
510 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
511 setStroke(g, borderStroke);
512
513 if (points.isClosed()) {
514 g.strokePolygon(xArray, yArray, nPoints);
515 } else {
516 g.strokePolyline(xArray, yArray, nPoints);
517 }
518 }
519
520 g.restore();
521 }
522
523 @Override
524 public void drawLine(int x1, int y1, int x2, int y2, Colour colour, Stroke stroke)
525 {
526 if (colour == null || stroke == null) return;
527
528 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
529
530 g.save();
531
532 g.setStroke(JavaFXConversions.toJavaFXColor(colour));
533 setStroke(g, stroke);
534
535 g.beginPath();
536 g.moveTo(x1 + 0.5, y1 + 0.5);
537 g.lineTo(x2 + 0.5, y2 + 0.5);
538 g.stroke();
539
540 g.restore();
541 }
542
543 @Override
544 public void drawString(String string, Point position, Font font, Colour colour)
545 {
546 // TODO: Implement. cts16
547 }
548
549 @Override
550 public void drawTextLayout(TextLayout layout, Point position, Colour colour)
551 {
552 if (layout == null || position == null || colour == null) return;
553
554 JavaFXTextLayoutManager layoutManager = JavaFXMiscManager.getIfUsingJavaFXTextLayoutManager();
555 if (layoutManager == null) {
556 drawString(layout.getLine(), position, layout.getFont(), colour);
557 return;
558 }
559
560 javafx.scene.text.Text text = layoutManager.getInternalLayout(layout);
561
562 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
563
564 g.save();
565
566 g.setFont(text.getFont());
567 g.setTextAlign(text.getTextAlignment());
568
569 g.setFill(JavaFXConversions.toJavaFXColor(colour));
570
571 g.fillText(text.getText(), position.getX(), position.getY());
572
573 g.restore();
574 }
575
576 @Override
577 public void setCursor(Cursor cursor)
578 {
579 // TODO: Implement. cts16
580 }
581
582 @Override
583 public Dimension getBestCursorSize(Dimension desiredSize)
584 {
585 // TODO: Implement. cts16
586 return new Dimension(32, 32);
587 }
588
589 @Override
590 public boolean showDialog(String title, String message)
591 {
592 // TODO: Implement. cts16
593 return false;
594 }
595
596 @Override
597 public Dimension getCurrentDrawingSurfaceSize()
598 {
599 Canvas currentSurface = _surfaceStack.getCurrentSurface();
600
601 int width = (int) currentSurface.getWidth();
602 int height = (int) currentSurface.getHeight();
603
604 return new Dimension(width, height);
605 }
606
607 private Point translateCanvasToScreen(Point p)
608 {
609 if (p == null) return null;
610
611 Bounds canvasBounds = ExpediteeApplication.theCanvas.localToScreen(ExpediteeApplication.theCanvas.getBoundsInLocal());
612
613 return p.clone().add(canvasBounds.getMinX(), canvasBounds.getMinY());
614 }
615
616 private Dimension getDecorationDimensions()
617 {
618 int decorWidth = (int) (ExpediteeApplication.theStage.getWidth() - ExpediteeApplication.theScene.getWidth());
619 int decorHeight = (int) (ExpediteeApplication.theStage.getHeight() - ExpediteeApplication.theScene.getHeight());
620
621 return new Dimension(decorWidth, decorHeight);
622 }
623
624 private void setStroke(GraphicsContext g, Stroke stroke)
625 {
626 g.setLineWidth(stroke.thickness);
627 g.setLineCap(JavaFXConversions.toJavaFXStrokeLineCap(stroke.capType));
628 g.setLineJoin(JavaFXConversions.toJavaFXStrokeLineJoin(stroke.joinType));
629 g.setLineDashes(Util.toDoubleArray(stroke.dashArray));
630 g.setLineDashOffset(stroke.dashPhase);
631 }
632
633 private Set<Image> createIconSet(Image image)
634 {
635 if (image == null) return null;
636
637 Set<Image> iconSet = new HashSet<Image>();
638
639 for (int size = MAX_ICON_SIZE; size >= MIN_ICON_SIZE; size >>= 1) {
640 Image tempImage = Image.createImage(size, size);
641 this.pushDrawingSurface(tempImage);
642 this.drawImage(image, Point.ORIGIN, new Dimension(size, size));
643 this.popDrawingSurface();
644 iconSet.add(tempImage);
645 }
646
647 return iconSet;
648 }
649
650 public static class ExpediteeApplication extends Application {
651
652 public static Stage theStage = null;
653 public static Scene theScene = null;
654 public static Group theGroup = null;
655 public static Canvas theCanvas = null;
656
657 public static JavaFXGraphicsManager manager = null;
658
659 @Override
660 public void start(Stage primaryStage) throws Exception
661 {
662 // Set up the canvas for drawing operations
663 theStage = primaryStage;
664 theGroup = new Group();
665 theScene = new Scene(theGroup);
666 theCanvas = new Canvas();
667 theGroup.getChildren().add(theCanvas);
668
669 // Set the scene
670 manager.setWindowIcon();
671 primaryStage.setScene(theScene);
672 primaryStage.show();
673
674 Platform.runLater(new Runnable() {
675 @Override
676 public void run() {
677 synchronized (_jfxAppThread) {
678 _jfxAppThread.notify();
679 }
680 }
681 });
682 }
683
684 }
685
686 @Override
687 public int getFontHeight(final Font font) {
688 final javafx.scene.text.Font fxFont = JavaFXFontManager.getInstance().getInternalFont(font);
689 return (int) fxFont.getSize();
690 }
691
692}
Note: See TracBrowser for help on using the repository browser.