Porting GeomLab to Java FX

Copyright © 2024 J. M. Spivey
Jump to navigation Jump to search

At the moment, GeomLab has a GUI built with Swing, and it's high time it was updated to use Java FX instead. This is an implementation project, and will produce software that will actually be used in schools.

  • The top-level GUI of GeomLab needs converting from Swing to Java FX. This should basically be a straightforward translation, but there will be many places where Java FX provides opportunities to do a better job.
  • The graphical output of GeomLab programs uses a graphics interface that already has multiple implementations (for AWT and for Postscript) and has a high degree of decoupling. It will be interesting to add another implementation into the mix.
  • As an added task, the delivery mechanism for GeomLab relies on downloading a JAR file and running it on a pre-installed JRE. It would be great to smooth this process!

Graphics interfaces

The decoupling between the picture-making functions of GeomLab and the platform toolkit replies on two interfaces that are implemented in a platform-dependent way: Native and Stylus

Native.Image

The interface Native.Image describes an image that can be accessed as a two-dimensional array of pixels. On AWT, Native.Image is implemented as by AWTFactory.AWTImage as a wrapper around an AWT BufferedImage object with type TYPE_RGB. There are methods as follows, all with an obvious meaning.

  • int getWidth()
  • int getHeight()
  • int getRGB(int x, int y) – returns a pixel as a 24-bit RGB value
  • void setRGB(int x, int y, int rgb) – sets a pixel to a given RGB value
  • Object getNative() – returns the underlying BufferedImage object

Native

The abstract class Native supports several methods that return Native.Image objects, each implemented for AWT by creating a BufferedImage object and wrapping it as an AWTImage.

  • Image image(int w, int h) – create a blank image
  • Image render(Drawable pic, int w, int h, double slider, ColorValue background) – create an image by rendering the picture pic. Typically , this is implemented by creating a native image (a BufferedImage for AWT) of size w by h, creating a Stylus object s for painting on it, then calling pic.draw(s, slider, background) and finally wrapping the image as a Native.Image. For AWT, the stylus s is created as an instance of ScreenStylus (see below).
  • Image readImage(InputStream s) throws IOException – read an image from a file.
  • void writeImage(Image img, String format, OutputStream s) throws IOException – write an image on a file in a specified format. (Both of these are implemented for AWT using the ImageIO library.)

The Native class also supports a few methods for getting the 'native' counterparts to graphical objects that have an internal representation in GeomLab. There is provision in the classes for the internal representations to cache these native counterparts, so that they are not computed repeatedly for no reason. For example, in GeomLab, there is an internal class ColorValue whose instances represent colours, and AWTFactory provides a method

  • Object color(ColorValue c)

that returns the corresponding instance of java.awt.Color. This is used by ColorValue objects to cache and return the corresponding AWT colour when it is needed (e.g., by ScreenStylus) to do actual drawing. The implementation of AWTFactory.color(c) is simple: it just returns

new java.awt.Color(c.rgb)

There are other methods of Native for vectors and transforms:

  • Object vector(Vec2D v)
  • Object transform(Tran2D t)

Each returns (if needed) a native counterpart to one of GeomLab's internal values. (As it happens, the ScreenStylus class doesn't need to compute native vectors, so the vector method raises an error (or returns null).)

Stylus

A Stylus is an object rather like a Graphics context in AWT – and in fact is implemented for AWT by a wrapper ScreenStylus around a Graphics2D object. Some of the methods are specific to GeomLab

Minimal requirements:

  • void setStroke(double width) – Set the stroke width for future drawing operations.
  • void drawStroke(Vec2D stroke[]) – Draw a polygon in black.
  • void fillOutline(Vec2D outline[], ColorValue color) – Fill a polygonal outline.
  • void drawLine(Vec2D from, Vec2D to, ColorValue color) – Draw a line.
  • void drawArc(Vec2D centre, double xrad, double yrad, double start, double extent, ColorValue color) – draw a transformed arc.
  • void fillOval(Vec2D centre, double xrad, double yrad, ColorValue color) – Fill an oval.
  • void drawImage(Native.Image image) – Draw a raster image.
  • boolean isTiny(Tran2D t) – Test if the transform t yields negligibly small results.

Stylus.Sketch

A Sketch is a picture that knows how to draw itself using a stylus.

(To be continued)