Skip to main content

Imagine - Architecture

This application is designed as a set of NetBeans modules that plug into the NetBeans Platform to form a complete software application.

Each logical piece of user interface is implemented as a separate NetBeans module - for example, the layer display component is implemented in the Layers UI module; the tool palette is implemented in the Tools UI module. The important thing is that there are no interdependencies between most components of the application. Here is the list of modules. The names of those which have a public API are underlined:

  • Color Chooser Lib — a library wrapper for the popup color chooser Swing component.
  • Effects — Implementations of some Effects.
  • Effects UI — Module that provides the Effects main menu and code to apply effects to the layer being edited.
  • General Tool Customizers — Provides customizer components for standard things like Number types, Boolean, Font, String, etc.
  • Image Cache Diagnostics — A module that shows fragmentation and file usage for the memory mapped undo data cache.
  • Layers UI — Provides the component that shows the list of layers and allows the active layer to be selected
  • Misc Swing Components — Provides some convenience UI components and UI delegates such as the popup JSlider UI delegate used by numeric customizers; and a LayoutManager that aligns columns of nested components.
  • NIO Image Support — Implements BufferedImages whose backing storage is a memory mapped file, not the Java heap.
  • Paint API — The main API for image editing
  • Painting Tools — The standard set of tools available in the application
  • Paint UI — The image editor implementation; also provides most main menu actions and shortcuts.
  • Raster Layers — Module which provides raster (bitmap backed, resolution dependent) layers for editing
  • Spline Model — Defines a model for editing shapes as a set of bezier splines
  • Tool Configuration UI — Provides the tool customizer UI component that tracks the current instance of Tool in Utilities.actionsGlobalContext().
  • Tools UI — Provides the tool palette component and the Tools main menu.
  • Vector Editor Layers — Provides an implementation of Layer that stores its backing data as a stack of Shape objects. Tolls can simply draw into the Graphics object the Surface provides; drawing operations are saved as a set of operations that can be replayed.
  • Vector Primitives — Provides serializable wrapper classes for all drawing operations that can be done on a Graphics2D.

The Paint API module provides a set of image editor APIs. The various pieces of the application communicate with each other using this API.

In particular, the Paint API module replaces the standard selection model in NetBeans with one based on the active editor instead of whatever component has focus (you wouldn't want the tool customizer to be emptied when you give it focus because it's tracking the active editor and that has disappeared from the global selection context).

The Paint API defines three classes in particular that are the core things tools and such will want to interact with:

  • Picture - represents an image the user is editing, which is composed of one or more Layer objects.
  • Layer - One editable layer in an picture. A picture is essentially a stack of independently editable layers which are composited together. Different types of Layer can be plugged in, with their own tool sets and abilities. The most basic form stores its data as Java BufferedImages and allows the user to paint into them via Tools. A Layer has attributes such as location, name, opacity. A Layer can provide an instance of Selection in its Lookup, which can be used to manipulate the selection.
  • Surface - A Layer has a Surface object which is the the actual drawing surface (a thing you can get a java.awt.Graphics2D from to draw into).
Note that all of the above are final classes. There are corresponding SPI classes PictureImplementation, LayerImplementation and SurfaceImplementation that can be implemented to provide your own implementation of these things.

The Paint UI module, which supplies the editor, provides an implementation of Picture; the Raster Layers module and the Vector Layers module both provide implementations of Layer and Surface.

At any time, if an editor is open, there is usually an active layer, which is the layer that the user is currently editing. The current instance of Picture and the active layer can be found from the global selection context:

Picture picture = Utilities.actionsGlobalContext().lookup(Picture.class);
assert picture.getActiveLayer() == Utilities.actionsGlobalContext().lookup(Layer.class);
        

Implementing Tools

Writing a tool is relatively simple. The Tool interface itself is mostly methods for getting an icon and display name for the tool. The activate(Layer) method is called when the user selects this tool.

The secret is what other interfaces your tool implements. If a Tool implements MouseListener, it will receive mouse events from the editor canvas, and it can call layer.getSurface().getGraphics() to draw into the layer. If it implements KeyListener, it will receive key events. Note that all mouse events are properly translated and scaled, so if the editor is zoomed in, the Tool does not have to do anything special - the coordinate space of incoming mouse events is scaled appropriately.

Other interfaces a Tool can implement are

  • PaintParticipant - meaning that this tool wants to draw on top of the canvas, or set the cursor on it, or similar, in response to mouse motion events - but do so without necessarily drawing anything into the image.
  • CustomizerProvider - indicates that this tool wants to provide a customizer component for customizing its settings, etc.

Tools and Customizers

The General Tool Customizers module provides some basic components for customizers that can be useful for creating tool customizers. For example, if you want a customizer for the thickness of drawn lines, you can call
Customizer<Integer> = Customizers.getCustomizer (Integer.class,
    Constants.STROKE, 0, 100);
        
and the result will have a consistent appearance with other customizers that let you configure stroke width, and it will be pre-set to the last user-set value, either from a previous session or the last time it was used.

Most tools have more than one parameter to configure, so it is easy to create aggregate customizers as follows:

        return new AggregateCustomizer (null, customizer1, customizer2, ...)
        
Note: remember that if you use components from Customizers, you are sharing these components with other tools. So each call to getCustomizer() should ensure that the components are really parented to the component you will return - in other words, if you create a panel and cache it as the customizer for something, an integer customizer such as the one we fetched above will be removed from your panel when some other tool tries to use it in its own customizer.

Effects

An Effect is basically just a wrapped java.awt.Composite which manipulates raster data (an API which allows Effects to be based on BufferedImageOP is coming). An Effect has an Applicator that allows the effect's parameters to be configured and can then generate an appropriate instance of a Composite.

Selection

Instances of Layer typically posess a Selection, which is found by calling
Selection sel = activeLayer.getLookup().lookup(Selection.class);
        
A selection is a collection of objects of some sort - the exact type is determined by the layer that is implementing selection, but both the default layer types use a collection of java.awt.Shape objects. All Selection objects must support fetching the selection as a shape.

The selection can be listened on for changes.

Layer Types

Implementing a custom layer type is not trivial, but is certainly doable. You will need to implement the SPI classes LayerFactory, LayerImplementation and SurfaceImplementation. The LayerImplementation should have an instance of Selection in its Lookup.

By placing your LayerFactory in the default Lookup (by creating a flat file in META-INF/services in your JAR file), your new layer type will be available in the new layer popup menu.

Undo and Redo

An instance of org.openide.awt.UndoRedo.Manager is made available by the editor in the global selection context, whenever an editor is open. It can be used to add undoable edits as editing occurs.

For tools, you can simply call Surface.beginUndoableOperation(localizedName) draw what you want, then call Surface.endUndoableOperation(), and before/after snapshots will automatically be saved.

If you are implementing your own undo/redo support (say, in a custom layer type), bear in mind that keeping lots of bitmap data on the Java heap will run you out of memory very quickly. If you need to keep bitmaps, it is best to use the NIO Images module's ByteNIOBufferedImage, which creates BufferedImages whose data is stored in a memory-mapped file off the heap. These images are perfectly usable, but they bypass all hardware graphics acceleration, so they are not quick at all to paint; best to use these for caches and convert back to a standard, accelerated BufferedImage when they are actually needed. These are quite handy for undo snapshots that may never be reused.

 
 
Close
loading
Please Confirm
Close