Getting Started | Tools | Transforms | Brushes | Cut, Copy and Paste | Observables | FAQs
An easy-to-use toolkit for incorporating paint tools like those found in MacPaint or Microsoft Paint into a Java Swing or JavaFX application. Sorry, JMonet is not compatible with Android.
This project provides the paint capabilities found in WyldCard (an open-sourced clone of Apple's HyperCard).
Kernel or BufferedImageOp.BufferedImage object making it easy to import and export graphics.| Icon | Tool | Description |
|---|---|---|
| Airbrush | Paints translucent color or texture onto the canvas. | |
| Curve | Draws quadratic (Bezier) curves by clicking to specify points on the curve. | |
| Eraser | Removes paint from the canvas by restoring affected pixels to their fully-transparent state. | |
| Fill | Shades an enclosed area with paint using a flood-fill algorithm. | |
| Line | Draws straight lines; hold shift to restrict lines to 15-degree angles. |
|
| Oval | Draws filled or outlined oval shapes; hold shift to constrain boundary to circle. |
|
| Paintbrush | Draws paint on the canvas (using configurable stroke and color/texture). | |
| Pencil | Draws or erases a free-form, single-pixel path on the canvas. | |
| Polygon | Draws filled or outlined irregular polygons by clicking to specify points. Double-click to close the polygon; press esc to keep only the lines visible; hold shift to restrict line angles to 15 degree multiples. |
|
| Freeform | Draws a closed, free-form shape on the canvas. Click and drag to draw a path; release the mouse to close the shape. | |
| Rectangle | Draws filled or stroked rectangles on the canvas; hold shift to constrain boundary to a square. |
|
| Round Rect | Draws filled or stroked round-rectangles on the canvas. | |
| Shape | Draws filled or stroked regular polygons (i.e., shapes--triangles, squares, polygons, hexagons, etc.) | |
| Text | Draws rasterized text of configurable font, size and style on the canvas. Text remains editable until user clicks away. |
| Icon | Tool | Description |
|---|---|---|
| Lasso | Define a free-form selection boundary (marching ants) for clearing or moving paint. | |
| Selection | Define a selection rectangle whose underlying graphic can be moved or cleared (press delete) |
| Icon | Tool | Description |
|---|---|---|
| Rotate | Define a selection, then use the drag handle to free-rotate the selected graphic around its center. Hold shift to restrict rotation angle to 15-degree increments. | |
| Slant | Define a selection, then use the drag handles to apply an affine shear transform to the selected graphic. | |
| Scale | Define a selection, then expand or shrink the selected image by dragging a handle. Hold shift to maintain selection's original aspect ratio. | |
| Projection | Define a selection, then use the drag handles to project the image onto the geometry of an arbitrary quadrilateral. | |
| Perspective | Define a selection, then use the drag handles to warp the image onto an isosceles trapezoid, providing the effect of the left or right side of the image appearing nearer or farther from the viewer. | |
| Rubber Sheet | Similar to the projection transform, but utilizes a "rubber sheet" algorithm that preserves relative position over linearity. |
| Icon | Tool | Description |
|---|---|---|
| Arrow | A no-op tool (does not modify the canvas). | |
| Magnifier | Zoom in (scale the canvas) at the location clicked; hold shift to zoom out or ctrl to restore normal zoom. |
JMonet is published to Maven Central. Simply include the library in your Maven project's POM, like:
<dependency>
<groupId>com.defano.jmonet</groupId>
<artifactId>jmonet</artifactId>
<version>0.4.0</version>
</dependency>
... or your Gradle build script:
repositories {
mavenCentral()
}
dependencies {
compile 'com.defano.jmonet:jmonet:0.4.0'
}
JMonet integrates easily into Java Swing and JavaFX applications. Create a canvas node or panel and add it to a window in your application:
In Swing applications:
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(() -> {
// Create and show Swing frame
JFrame frame = new JFrame("My Pretty Picture");
frame.setPreferredSize(new Dimension(640, 480));
frame.pack();
frame.setVisible(true);
// Create a JMonet canvas and add it to the window
JMonetCanvas myCanvas = new JMonetCanvas(new Dimension(640, 480));
frame.getContentPane().add(myCanvas);
});
}
In JavaFX applications:
@Override
public void start(Stage stage) {
// Create a JFX node for our paint canvas
JFXPaintCanvasNode myCanvas = new JFXPaintCanvasNode(new JMonetCanvas(new Dimension(640, 480)));
// Create a pane for it
StackPane pane = new StackPane();
pane.getChildren().add(myCanvas);
// And add it to our stage
stage.setScene(new Scene(pane, 640, 480));
stage.show();
}
Start painting by making a tool active on the canvas with the PaintToolBuilder:
PaintTool paintbrush = PaintToolBuilder.create(PaintToolType.PAINTBRUSH)
.withStroke(StrokeBuilder.withShape().ofCircle(8).build())
.withStrokePaint(Color.RED)
.makeActiveOnCanvas(myCanvas)
.build();
Voila! You're ready to start painting a round, 8-pixel, bright red brushstroke onto your canvas. Don't forget to deactivate this tool when you're done with it or wish to start using another tool:
// When you're done painting or ready for a different tool...
paintbrush.deactivate();
There's no technical limitation that prevents multiple tools from being active on the same canvas at the same time, but that's not usually desired behavior in a paint program.
The following image transforms may be applied to an image selection (that is, a portion of the canvas selected by the lasso or selection rectangle tool). Once a selection has been made, invoke one of the following methods on the PaintTool object to transform its selection:
| Transform Method | Description |
|---|---|
adjustBrightness |
Changes the brightness/luminosity of all pixels in the selected image by adding delta to each red, green and blue color channel value (a value between 0..255, where 0 is completely dark, 255 is completely light). |
adjustTransparency |
Changes the opacity of each pixel in the selected image by adding delta the alpha channel (a value between 0..255 where 0 is fully transparent and 255 is fully opaque). |
removeTranslucency |
Makes translucent pixels either fully transparent of fully opaque. |
applyTransform |
Applies an AffineTransform or a PixelTransform to the selection. |
fill |
Fills any fully-transparent pixels in the selection with a given Paint. |
flipHorizontal |
Flips the selection about a vertical axis drawn through the center of the image. |
flipVertical |
Flips the selection about a horizontal axis drawn through the center of the image. |
invert |
Inverts the color |
pickupSelection |
"Picks up" the pixels on the canvas that are currently within the bounds of the selection and adds them to the selection. Useful when moving a selection over another region of the canvas. |
reduceColor |
Performs a color quantization and dithers the result using a specified dithering algorithm. See the com.defano.algo.dither package for available dithering implementations (or implement your own). |
reduceGreyscale |
Performs a luminosity quantization and dithers the result using a specified dithering algorithm. |
rotateLeft |
Rotates the selection 90 degrees counter-clockwise. |
rotateRight |
Rotates the selection 90 degrees clockwise. |
Each of these transforms is implemented as a standalone class in the com.defano.jmonet.transform package hierarchy, making it easy to apply these programmatically (offline) to a BufferedImage object.
A Stroke represents the size and shape of the "pen" used to draw the outline of a shape or path.
JMonet's StrokeBuilder class can generate complex brush strokes of any arbitrary shape (even the shape of text). For example, to produce a stroke in the shape of a vertical line, 20 pixels tall:
StrokeBuilder.withShape()
.ofVerticalLine(20)
.build();
Stroked shapes can be transformed, too. This produces a 20x10 parallelogram stroke:
StrokeBuilder.withShape()
.ofRectangle(20, 10)
.sheared(1, 0)
.build();
Shapes can be combined together to create a composite stroke shape. This example produces a stroke in the shape of the international no symbol:
StrokeBuilder.withShape()
.ofCircle(48) // draw circle
.outlined(6) // ... outlined with 6px border, not filled
.ofRectangle(6, 60) // draw slash (6px wide, 60px long)
.rotated(-45) // ... rotate it -45 degrees
.build();
Instances of Java's BasicStroke can also be created with this builder. This example produces an 8-pixel stroke with a 10-pixel dashed pattern:
StrokeBuilder.withBasicStroke()
.ofWidth(8)
.withDash(10)
.build();
JMonet makes it easy to integrate cut, copy and paste functions into an application that utilizes the operating system's clipboard. With this, you can copy and paste graphics from within your own application or between it and other applications.
A bit of integration is required to connect these functions to the UI elements in your app (like menu items or toolbar buttons) that a user will interact with.
ActionListener to route cut-copy-paste actions to the canvasConsistent with Swing's ActionListener pattern, the JMonet canvas will not receive cut, copy or paste actions until we register a listener class that routes clipboard actions to it. The CanvasClipboardActionListener class will attempt to route actions to whichever canvas currently in focus. If you dislike this behavior you may pass an implementation of CanvasFocusDelegate into its constructor, or simply write your own ActionListener instead.
Add your CanvasClipboardActionListener to whichever user interface elements will generate cut, copy and paste events. Most commonly, this would include menu items in the menu bar, but could also be used with buttons on a toolbar. For example:
JMenuItem myCopyMenuItem = new JMenuItem(new DefaultEditorKit.CopyAction());
copyMenuItem.setName("Copy");
copyMenuItem.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME))
copyMenuItem.addActionListener(new CanvasClipboardActionListener());
myEditMenu.add(myCopyMenuItem);
Next, your application needs to tell JMonet what it should do when it receives a cut, copy or paste action. This is accomplis
$ claude mcp add jmonet \
-- python -m otcore.mcp_server <graph>