Graphics 2D, AWT, Swing canvas.

For developing Java for desktop and browser, we can use easily use AWT and Swing libraries since they are supported out of the box since Haxe3. There are lots of libraries and approaches to developing with Java but for simple graphics, and components, AWT and Swing are a good place to start, unless you want to target phone apps. To be honest there is probably little reason in creating a Java applet to run in the browser except that it is an easier way to display something cool you made for the desktop.

Swing and AWT overlap in terms of component functionality, but Swing is more recent, so will be our preferred approach for components and structures, but AWT is useful for more basic functionality. The relationship and logic behind AWT and Swing is well beyond the scope of this tutorial, but for instance, our Main haxe class can extend a Frame ( AWT ) or a JFrame ( Swing ) both will work for creating applications, and to create a browser applet we would extend an Applet ( AWT ) or a JApplet ( Swing ) but lets stick with the Swing approaches.

I am very new to Java so this tutorial will not explain all aspects but rather provide workflows that work well in Haxe based on lots of reading around and practical application and hopefully save you a lot of time when trying to draw to your Java application.

Setup the Main class


So lets start looking at how we use Java 2D api's in Haxe. We create a main class and setup the view window, it needs to extend a frame, since we are using Swing components this frame is a JFrame
Let call our Main class Graphics2DExample.hx

Graphics2DExample.hx


package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;

class Graphics2DExample extends JFrame
{
    
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        // Show the view window.
        setVisible( true );
    }
    
}

We need to create a folder for the output files, lets call it java and we need a compile file compile.hxml
-java java
-main Graphics2DExample
-D haxe3

Once the Main class is compiled to Java we want to compile the Java code and run the Application, we can do this by adding --next and terminal commands, so lets modify our code.

compile.hxml


-java java
-main Graphics2DExample
-D haxe3
--next
-cmd cd java/src 
-cmd javac haxe/root/Graphics2DExample.java -d ../
-cmd cd ..
-cmd java haxe/root/Graphics2DExample

If we run the compile.hxml from our terminal/commandline
haxe compile.hxml

Ok not very exciting you should get a window with a title and a black background. If not then check that you have Java and Haxe setup properly by referring to other tutorials.

Drawing a Circle in our Application


Java uses a lot of inheritance and sometimes using Java 2D api can seem like magic, it is important when using Swing to work with the structures and let them do our DoubleBuffering for us, we just have to add the right magic words to avoid flickering and other render issues. I have found often it is just about calling the correct super parameter when extending a class or overriding a method to make sure the relevant drawing structures are called and setup for double buffering, if you want to manually control your double buffering then you probably need to switch to an AWT component workflow and spend a lot of time reading.. good luck :). Lets just take advantage of the higher level structures provided, for drawing we need a Panel or component to draw in. Swing has lots of built in components we can extend a JPanel for custom drawing. If you are used to flash or Javascript you can think of a panel/component as being like a div/sprite/movieclip, but instead of appendChild or addChild we just add it to our main classes content pane, eg: getContentPane().add( surface ). The actual drawing code needs to be done in an overridden Painting method, it kind of annoying but it seems to be setup this way so that it can optimize rendering for you. The best Painting method to override is the paintComponent. So we need to create a class then extends JPanel and has a method that overrides paintComponent and make sure we call all the supers with parameters to turn on the double buffering when we animate. Lets call our class Surface because we are going to use it like a canvas surface to draw on.

Surface.hx


package;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color;
import javax.swing.JPanel;
// extend a component to draw on
class Surface extends JPanel
{
    public function new() 
    {
        // turn on doublebuffering
        super( true );   
    }
    
    // draw in an overriden painting method
    @:overload public function paintComponent( g: Graphics )
    {
        // magic used to stop flickering when redrawing for animation later.
        super.paintComponent( g );
        // Strange but we need to cast so we can paint
        var g2D: Graphics2D = cast g;
        // make drawing antialias so smooth not pixelated edges
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY );
        // set drawing color
        g2D.setColor(Color.red);
        // draw a fill oval, x, y, wide, hi
        g2D.fillOval( 300, 120, 150, 150 );
        // release memory of graphics2d after finished drawing with it.
        g2D.dispose();
  }
  
}

As you can see the actual lines of code where we do drawing
        // set drawing color
        g2D.setColor(Color.red);
        // draw a fill oval, x, y, wide, hi
        g2D.fillOval( 300, 120, 150, 150 );

the rest is just template code that is unlikely to change. So if we want to create our own scenegraph render approach we might set an array of custom elements on the surface class and pass g2D to them.

Simple Scenegraph approach concept


package;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color;
import javax.swing.JPanel;
import my.CustomElement;
// extend a component to draw on
class Surface extends JPanel
{
    // Store a array of CustomElement with a render( g2D: Graphics2D ) method on the Surface to allow a custom scenegraph implementation.
    public var surfaceShapes: Array<CustomElement>;
    public function new() 
    {
        // turn on doublebuffering
        super( true );   
    }
    
    // draw in an overriden painting method
    @:overload public function paintComponent( g: Graphics )
    {
        // magic used to stop flickering when redrawing for animation later.
        super.paintComponent( g );
        // Strange but we need to cast so we can paint
        var g2D: Graphics2D = cast g;
        // make drawing antialias so smooth not pixelated edges
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY );
    for( surfaceShape in surfaceShapes ) surfaceShape.render( g2D );
        // release memory of graphics2d after finished drawing with it.
        g2D.dispose();
  }
  
}

But lets go back to the simpler circle example, so revert our surface class to the original. Now we need to modify the main class to see the red cicle.
package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
// import the extended JPanel we created.
import Surface;

class Graphics2DExample extends JFrame
{
    
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        
    // Add the new code before showing the window.
        var surface = new Surface();
        getContentPane().add( surface );
        
        // Show the view window.
        setVisible( true );
        
    }
    
}

Compiling this we should see a red circle in a window with a black background. You can explore some of the drawing methods available for yourself but let move to getting more interactive...

Keyboard Movement


There are a couple of approaches to setting up keyboard events but since often they are for the whole application it make sense for a small application to set them up in your main basically we just add interfaces for keyboard to the main class and the keyboard methods and you have keyboard support. So lets add arrow key movement to the circle, for clarity lets create our own method nudge for this functionality then we can choose which key listener type we set it up with.

nudge method


    function nudge( e: KeyEvent ){
        // get the x and y of the surface JPanel
        var location = surface.getLocation();
    // move the JPanel depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                surface.setLocation( location.x, location.y - 1 );
            case KeyEvent.VK_DOWN:
                surface.setLocation( location.x, location.y + 1 );
            case KeyEvent.VK_LEFT:
                surface.setLocation( location.x - 1, location.y );
            case KeyEvent.VK_RIGHT:
                surface.setLocation( location.x + 1, location.y );
        }
        // we call repaint on surface this automatically re-renders the Surface using double buffering so there is no flickering as the elements are redrawn,
        // in our case only a circle;
        surface.repaint();
    }

But we also need to setup so that we are focused for receiving key events and listener setup.
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();

In the main class we now tie the key pressed method to the nudge method and we can now move the circle with the arrow keys:

Graphics2DExample.hx


package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
import Surface;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

// notice class implements KeyListener
class Graphics2DExample
extends JFrame
implements KeyListener 
{
    var surface: Surface;
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        
        surface = new Surface();
        getContentPane().add( surface );
        
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();
        
        // Show the view window.
        setVisible( true );
        
    }
    
    public function keyTyped( e: KeyEvent ){}
    public function keyReleased(e: KeyEvent ){}
    public function keyPressed( e: KeyEvent ) nudge( e );
    
    function nudge( e: KeyEvent ){
        // get the x and y of the surface JPanel
        var location = surface.getLocation();
        // move the JPanel depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                surface.setLocation( location.x, location.y - 1 );
            case KeyEvent.VK_DOWN:
                surface.setLocation( location.x, location.y + 1 );
            case KeyEvent.VK_LEFT:
                surface.setLocation( location.x - 1, location.y );
            case KeyEvent.VK_RIGHT:
                surface.setLocation( location.x + 1, location.y );
        }
        // we call repaint on surface this automatically re-renders the Surface
        // using double buffering so there is no flickering 
        // as the elements are redrawn,
        // in our case only a circle.
        surface.repaint();
    }
}

AffineTransform


While it is great to use keys to move a component round the screen, it would be also useful to move elements drawn on our surface while leaving the surface jpanel and other elements where they were. AfflineTransform allows you to move, or more accurately transform a path, we can construct path in several ways such as using Polygon or GeneralPath. GeneralPath is quite nice because you construct it using lineTo, moveTo etc... so it has parallels with flash drawing api. Lets put the creation of a GeneralPath in a class to keep the rest of the code clean, for fun I have created Haxe logo vertices.
package;

import java.awt.geom.GeneralPath;

class HaxeVertices
{
    public var path: GeneralPath;
    public function new()
    {
        path = new GeneralPath();
        path.moveTo( 0, 0 );
        
        // draw outside
        path.lineTo( 40, 0 );
        path.lineTo( 75, 20 );
        path.lineTo( 110, 0 );
        path.lineTo( 150, 0 );
        
        path.lineTo( 150, 40 );
        path.lineTo( 130, 75 );
        path.lineTo( 150, 110 );
        path.lineTo( 150, 150 );
        
        path.lineTo( 110, 150 );
        path.lineTo( 75, 130 );
        path.lineTo( 40, 150 );
        path.lineTo( 0, 150 );
        
        path.lineTo( 0, 110 );
        path.lineTo( 20, 75 );
        path.lineTo( 0, 40 );
        path.lineTo( 0, 0 );
        
        // draw small triangles
        path.lineTo( 75, 20 );
        path.lineTo( 150, 0 );
        
        path.lineTo( 130, 75 );
        path.lineTo( 150, 150 );
        
        path.lineTo( 75, 130 );
        path.lineTo( 0, 150 );
        
        path.lineTo( 20, 75 );
        path.lineTo( 0, 0 );
        
        // centre diamond 
        path.moveTo( 75, 20 );
        path.lineTo( 130, 75 );
        path.lineTo( 75, 130 );
        path.lineTo( 20, 75 );
        path.lineTo( 75, 20 );
    }
}

Now we can add a method to enable us to translate the path.
 
package;

import java.awt.geom.GeneralPath;
import java.awt.geom.AffineTransform;

class HaxeVertices
{
    public var path: GeneralPath;
    public var x: Float;
    public var y: Float;
    public function new()
    {
        x = 0;
        y = 0;
        path = new GeneralPath();
        path.moveTo( 0, 0 );
        
        // draw outside
        path.lineTo( 40, 0 );
        path.lineTo( 75, 20 );
        path.lineTo( 110, 0 );
        path.lineTo( 150, 0 );
        
        path.lineTo( 150, 40 );
        path.lineTo( 130, 75 );
        path.lineTo( 150, 110 );
        path.lineTo( 150, 150 );
        
        path.lineTo( 110, 150 );
        path.lineTo( 75, 130 );
        path.lineTo( 40, 150 );
        path.lineTo( 0, 150 );
        
        path.lineTo( 0, 110 );
        path.lineTo( 20, 75 );
        path.lineTo( 0, 40 );
        path.lineTo( 0, 0 );
        
        // draw small triangles
        path.lineTo( 75, 20 );
        path.lineTo( 150, 0 );
        
        path.lineTo( 130, 75 );
        path.lineTo( 150, 150 );
        
        path.lineTo( 75, 130 );
        path.lineTo( 0, 150 );
        
        path.lineTo( 20, 75 );
        path.lineTo( 0, 0 );
        
        // centre diamond 
        path.moveTo( 75, 20 );
        path.lineTo( 130, 75 );
        path.lineTo( 75, 130 );
        path.lineTo( 20, 75 );
        path.lineTo( 75, 20 );
    }
    
    public function setLocation( x_: Float, y_: Float )
     {
         path.transform( AffineTransform.getTranslateInstance( x_ - x, y_ - y ) );
         x = x_; 
         y = y_;
     }
    
}

Obviously we could do rotation, or a shear instead. But translate is useful because often we want to move parts of an image around.
So lets update our surface so that it can draw a GeneralPath, lets make it orange..
package;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.RenderingHints;
import java.awt.Color;
import javax.swing.JPanel;

// extend a component to draw on
class Surface extends JPanel
{
    public var path: GeneralPath;
    public function new() 
    {
        // turn on doublebuffering
        super( true );   
    }
    
    // draw in an overriden painting method
    @:overload public function paintComponent( g: Graphics )
    {
        // magic used to stop flickering when redrawing for animation later.
        super.paintComponent( g );
        // Strange but we need to cast so we can paint
        var g2D: Graphics2D = cast g;
        // make drawing antialias so smooth not pixelated edges
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY );
        // set drawing color
        g2D.setColor(Color.red);
        // draw a fill oval, x, y, wide, hi
        g2D.fillOval( 300, 120, 150, 150 );
        
        // Added Orange haxevertices generalpath
        g2D.setColor( Color.ORANGE );
        g2D.draw( path );
        
        // release memory of graphics2d after finished drawing with it.
        g2D.dispose();
  }
  
}

And we need to update our key control code in the main class and pass the HaxeVerticies to the surface path variable.
package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
import Surface;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import HaxeVertices;

// notice class implements KeyListener
class Graphics2DExample
extends JFrame
implements KeyListener 
{
    var surface:        Surface;
    var haxeVertices:   HaxeVertices;
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        
        surface = new Surface();
        getContentPane().add( surface );
        
        // create haxe vertices general path
        haxeVertices = new HaxeVertices();
        // set the path drawn on the surface to the haxe vertices
        surface.path = haxeVertices.path;
        
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();
        
        // Show the view window.
        setVisible( true );
        
    }
    
    public function keyTyped( e: KeyEvent ){}
    public function keyReleased(e: KeyEvent ){}
    public function keyPressed( e: KeyEvent ) nudge( e );
    
    function nudge( e: KeyEvent ){
        // move the path ( GeneralPath ) depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y - 1 );
            case KeyEvent.VK_DOWN:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y + 1 );
            case KeyEvent.VK_LEFT:
                haxeVertices.setLocation( haxeVertices.x - 1, haxeVertices.y );
            case KeyEvent.VK_RIGHT:
                haxeVertices.setLocation( haxeVertices.x + 1, haxeVertices.y );
        }
        surface.repaint();
    }
}

So now we can move Paths drawn to the surface around with the arrow keys. Next we will look at Mouse interaction.

Mouse Interactiion


Mouse events are often setup in a similar way to keyboard events, but they are split into movement and into click/presses. So we need to add a couple more interfaces to our main class.
class Graphics2DExample
extends JFrame
implements KeyListener 
implements MouseListener 
implements MouseMotionListener

and set them up
        addMouseListener( this );
        addMouseMotionListener( this );

and add the mouse event methods specified in the interfaces. Now like we did with the nudge earlier, I am going to create a method to do on a mouse event, then later decide what to attach it to. With a GeneralPath we can check if a point is contained within, with the contains method, it does not seem to be really acurate hittest but for a rough dragging it's fine.
    function moveHaxeVertices( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            haxeVertices.setLocation( mousePos.x - 75., mousePos.y - 75. );
        // make sure the surface is redrawn.
            surface.repaint();
        }
    }

To use this for dragging we can just call it from the mouseDragged method.
    public function mouseDragged( e: MouseEvent ) moveHaxeVertices( e );

So the main class now looks like...
package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
import Surface;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import HaxeVertices;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;

// notice class implements KeyListener
class Graphics2DExample
extends JFrame
implements KeyListener 
implements MouseListener 
implements MouseMotionListener
{
    var surface:        Surface;
    var haxeVertices:   HaxeVertices;
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        
        surface = new Surface();
        getContentPane().add( surface );
        
        // create haxe vertices general path
        haxeVertices = new HaxeVertices();
        // set the path drawn on the surface to the haxe vertices
        surface.path = haxeVertices.path;
        
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();
        
        // add Mouse interaction
        addMouseListener( this );
        addMouseMotionListener( this );
        
        
        // Show the view window.
        setVisible( true );
        
    }
    
    public function mousePressed( e: MouseEvent ) {}
    public function mouseDragged( e: MouseEvent ) moveHaxeVertices( e );
    public function mouseExited( e: MouseEvent ) {}
    public function mouseMoved( e: MouseEvent ) {}  
    public function mouseEntered( e: MouseEvent ) {}  
    public function mouseClicked( e: MouseEvent ) {}  
    public function mouseReleased( e: MouseEvent ) {}
    
    public function keyTyped( e: KeyEvent ){}
    public function keyReleased(e: KeyEvent ){}
    public function keyPressed( e: KeyEvent ) nudge( e );
    
    function moveHaxeVertices( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            haxeVertices.setLocation( mousePos.x - 75., mousePos.y - 75. );
            surface.repaint();
        }
    }
    
    function nudge( e: KeyEvent ){
        // move the path ( GeneralPath ) depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y - 1 );
            case KeyEvent.VK_DOWN:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y + 1 );
            case KeyEvent.VK_LEFT:
                haxeVertices.setLocation( haxeVertices.x - 1, haxeVertices.y );
            case KeyEvent.VK_RIGHT:
                haxeVertices.setLocation( haxeVertices.x + 1, haxeVertices.y );
        }
        surface.repaint();
    }
}

If we want to create a rollover cursor change we could check if the mouse was above the HaxeVerticies object by adding an overCheck.
    public function overCheck( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ));
        } 
        else
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ));
        }
    }

and modify the mouseMoved function
public function mouseMoved( e: MouseEvent ) overCheck( e );

I have just found that the contains code used for hittesting is offset by the title header, we can remove the header title
setUndecorated( true ); 

We now have an idea of basic drawing, approaches to keyboard and mouse interaction but we need to be able to draw images on screen.

BufferedImage


Java has an Image class but we normally work with it's subclass BufferedImage, because it has an accessible data buffer, with methods for storing, interpreting, and obtaining pixel data. Since any runtime call to load a resource could fail we have to wrap the call in a try, and to keep the compiler happy we return a default internally generated BufferImage this allows us to limit the try to just the image load, by putting it in a separate method we isolate the messy try code. Since loading an image with an applet is dfferent from loading into an application lets add a compiler flag for the applet option so that it is easier later.
    inline static var   imageSrc:   String = "tablecloth.jpg";
    public static function bufferedImage( imageSrc: String ): BufferedImage
    {
        try{
            #if applet
                return getImage( getCodeBase(), imageSrc );
            #else
                return ImageIO.read( new File( imageSrc ) );
            #end
        }
        catch( e: IOException ) trace('image load fail');
        
        return new BufferedImage( 100, 50, BufferedImage.TYPE_INT_ARGB );
    }

We need to add the image inside our java folder or else we won't be able to load the image.
if we pass the buffered image to our surface classes paintComponent, we can position it at 0, 0
        g2D.drawImage( bufferedImage, 0, 0, null );

So the code now looks like
package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
import Surface;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import HaxeVertices;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.Cursor;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

class Graphics2DExample
extends JFrame
implements KeyListener 
implements MouseListener 
implements MouseMotionListener
{
    var surface:        Surface;
    var haxeVertices:   HaxeVertices;
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        super('Graphics 2D Example');
        // Specify to use Hardware rendering.
        System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        
        setUndecorated( true );
        
        // Add a close button to window.
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        
        // Centre the view window in centre of users screen.
        setLocationRelativeTo(null);
        
        surface = new Surface();
        
        getContentPane().add( surface );
        
        // create haxe vertices general path
        haxeVertices = new HaxeVertices();
        // set the path drawn on the surface to the haxe vertices
        surface.path = haxeVertices.path;
        
        // create a buffered image and set it on the surface
        surface.bufferedImage = bufferedImage( imageSrc );
        
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();
        
        // add Mouse interaction
        addMouseListener( this );
        addMouseMotionListener( this );
        
        
        // Show the view window.
        setVisible( true );
        
    }
    
    inline static var   imageSrc:   String = "tablecloth.jpg";
    public static function bufferedImage( imageSrc: String ): BufferedImage
    {
        try{
            #if applet
                return getImage( getCodeBase(), imageSrc );
            #else
                return ImageIO.read( new File( imageSrc ) );
            #end
        }
        catch( e: IOException ) trace('image load fail');
        
        return new BufferedImage( 100, 50, BufferedImage.TYPE_INT_ARGB );
    }
    
    public function mousePressed( e: MouseEvent ) {}
    public function mouseDragged( e: MouseEvent ) moveHaxeVertices( e );
    public function mouseExited( e: MouseEvent ) {}
    public function mouseMoved( e: MouseEvent ) overCheck( e );
    public function mouseEntered( e: MouseEvent ) {}  
    public function mouseClicked( e: MouseEvent ) {}  
    public function mouseReleased( e: MouseEvent ) {}
    
    public function keyTyped( e: KeyEvent ){}
    public function keyReleased(e: KeyEvent ){}
    public function keyPressed( e: KeyEvent ) nudge( e );
    
    public function overCheck( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ));
        } 
        else
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ));
        }
    }
    
    function moveHaxeVertices( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            haxeVertices.setLocation( mousePos.x - 75., mousePos.y - 75. );
            
            surface.repaint();
        }
    }
    
    function nudge( e: KeyEvent ){
        // move the path ( GeneralPath ) depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y - 1 );
            case KeyEvent.VK_DOWN:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y + 1 );
            case KeyEvent.VK_LEFT:
                haxeVertices.setLocation( haxeVertices.x - 1, haxeVertices.y );
            case KeyEvent.VK_RIGHT:
                haxeVertices.setLocation( haxeVertices.x + 1, haxeVertices.y );
        }
        
        surface.repaint();
    }
}

and
package;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.RenderingHints;
import java.awt.Color;
import javax.swing.JPanel;
import java.awt.image.BufferedImage;

// extend a component to draw on
class Surface extends JPanel
{
    public var path: GeneralPath;
    public var bufferedImage: BufferedImage;
    
    public function new() 
    {
        // turn on doublebuffering
        super( true );   
    }
    
    // draw in an overriden painting method
    @:overload public function paintComponent( g: Graphics )
    {
        // magic used to stop flickering when redrawing for animation later.
        super.paintComponent( g );
        // Strange but we need to cast so we can paint
        var g2D: Graphics2D = cast g;
        
        // make drawing antialias so smooth not pixelated edges
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY );
        
        g2D.drawImage( bufferedImage, 0, 0, null );
        
        // set drawing color
        g2D.setColor(Color.red);
        // draw a fill oval, x, y, wide, hi
        g2D.fillOval( 300, 120, 150, 150 );
        
        // Added Orange haxevertices generalpath
        g2D.setColor( Color.ORANGE );
        g2D.draw( path );
        
        // release memory of graphics2d after finished drawing with it.
        g2D.dispose();
  }
  
}

If we want to only use part of the image we can add width and height parameters.
 g2D.drawImage( bufferedImage, 0, 0, wide, hi, null );

And we can further crop that to a generalPath like using a mask.
        g2D.setClip( generalPath );  
        g2D.drawImage(  bufferedImage, 0, 0, wide, hi, this );

Alternatively we can clip it using slightly different commands the result is a slightly smaller croped image ( by the border edge )
    g2D.setPaint( new TexturePainnt( bufferedImage, new Rectangle( 0, 0, wide, hi ) );
    g2D.fill( generalPath );

Applets and Java on the Web


While applets in a browser are not popular they are a good way to share your Java desktop code with fellow haxe java developers. To use an Applet in a browser mostly you can just swap the JFrame for a JApplet, we will need to import the JApplet class.
import java.applet.Applet;

However any code that requires system or file access will need to be amended or commented out that includes setting a close button, and centering the app, the JApplet super no longer takes a string argument, and is just changed to super(). So our HaxeVertices class remains the same but the other two classes are changed:

Graphics2DExample


package;

import java.lang.System;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JApplet;
import java.awt.Color;
import Surface;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import HaxeVertices;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.Cursor;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

class Graphics2DExample
extends JApplet
implements KeyListener 
implements MouseListener 
implements MouseMotionListener
{
    var surface:        Surface;
    var haxeVertices:   HaxeVertices;
    public static function main() 
    { 
        new Graphics2DExample(); 
    } 
    
    public function new()
    {
        // The view windows title.
        //super('Graphics 2D Example');
        super();
        // Specify to use Hardware rendering. Commented out for a JApplet
        //System.setProperty("sun.java2d.opengl","True");
        // Set window size to 700 pixels wide by 500 pixels high.
        setSize( 700, 500 );
        
        //setUndecorated( true );
        
        // Add a close button to window.
        //setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        // Set the background to black.
        setBackground( Color.black );
        
        // Centre the view window in centre of users screen.
        //setLocationRelativeTo(null);
        
        surface = new Surface();
        
        getContentPane().add( surface );
        
        // create haxe vertices general path
        haxeVertices = new HaxeVertices();
        // set the path drawn on the surface to the haxe vertices
        surface.path = haxeVertices.path;
        
        // create a buffered image and set it on the surface
        //surface.bufferedImage = bufferedImage( imageSrc );
        
        addKeyListener( this );
        setFocusable(true);
        requestFocusInWindow();
        
        // add Mouse interaction
        addMouseListener( this );
        addMouseMotionListener( this );
        
        
        // Show the view window.
        setVisible( true );
        
    }
    
    inline static var   imageSrc:   String = "tablecloth.jpg";
    public static function bufferedImage( imageSrc: String ): BufferedImage
    {
        try{
            #if applet
                return getImage( getCodeBase(), imageSrc );
            #else
                return ImageIO.read( new File( imageSrc ) );
            #end
        }
        catch( e: IOException ) trace('image load fail');
        
        return new BufferedImage( 100, 50, BufferedImage.TYPE_INT_ARGB );
    }
    
    public function mousePressed( e: MouseEvent ) {}
    public function mouseDragged( e: MouseEvent ) moveHaxeVertices( e );
    public function mouseExited( e: MouseEvent ) {}
    public function mouseMoved( e: MouseEvent ) overCheck( e );
    public function mouseEntered( e: MouseEvent ) {}  
    public function mouseClicked( e: MouseEvent ) {}  
    public function mouseReleased( e: MouseEvent ) {}
    
    public function keyTyped( e: KeyEvent ){}
    public function keyReleased(e: KeyEvent ){}
    public function keyPressed( e: KeyEvent ) nudge( e );
    
    public function overCheck( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ));
        } 
        else
        {
            e.getComponent().setCursor(Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ));
        }
    }
    
    function moveHaxeVertices( e: MouseEvent )
    {
        var mousePos = e.getPoint();
        if( haxeVertices.path.contains( mousePos ) )
        {
            haxeVertices.setLocation( mousePos.x - 75., mousePos.y - 75. );
            
            surface.repaint();
        }
    }
    
    function nudge( e: KeyEvent ){
        // move the path ( GeneralPath ) depending on key code
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_UP:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y - 1 );
            case KeyEvent.VK_DOWN:
                haxeVertices.setLocation( haxeVertices.x, haxeVertices.y + 1 );
            case KeyEvent.VK_LEFT:
                haxeVertices.setLocation( haxeVertices.x - 1, haxeVertices.y );
            case KeyEvent.VK_RIGHT:
                haxeVertices.setLocation( haxeVertices.x + 1, haxeVertices.y );
        }
        
        surface.repaint();
    }
}

Surface.hx


package;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.RenderingHints;
import java.awt.Color;
import javax.swing.JPanel;
import java.awt.image.BufferedImage;

// extend a component to draw on
class Surface extends JPanel
{
    public var path: GeneralPath;
    public var bufferedImage: BufferedImage;
    
    public function new() 
    {
        // turn on doublebuffering
        super( true );   
    }
    
    // draw in an overriden painting method
    @:overload public function paintComponent( g: Graphics )
    {
        // magic used to stop flickering when redrawing for animation later.
        super.paintComponent( g );
        // Strange but we need to cast so we can paint
        var g2D: Graphics2D = cast g;
        
        // make drawing antialias so smooth not pixelated edges
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY );
        
        //g2D.drawImage( bufferedImage, 0, 0, this );
        
        // set drawing color
        g2D.setColor(Color.red);
        // draw a fill oval, x, y, wide, hi
        g2D.fillOval( 300, 120, 150, 150 );
        
        // Added Orange haxevertices generalpath
        g2D.setColor( Color.ORANGE );
        g2D.draw( path );
        
        // release memory of graphics2d after finished drawing with it.
        g2D.dispose();
  }
  
}

Next we change the way we run the Java code and add an index.html file in our java folder.

So on the last line of our Compile.hx file we change the java run command to an appletviewer command.

Compile.hx


-java java
-main Graphics2DExample
-D haxe3
--next
-cmd cd java/src 
-cmd javac haxe/root/Graphics2DExample.java -d ../
-cmd cd ..
#-cmd java haxe/root/Graphics2DExample
-cmd appletviewer index.html

This is the easiest way to test an applet.

If we want to run it in a browser we probably need to setup a server especially if you want to load images at runtime. On a mac when testing locally we can start Twisted from the terminal to create a local host change the path to match the path of your 'java' deploy folder.

twistd -n web --path=/Users/Me/haxejavaexample/java/ --port="tcp:8080:interface=0.0.0.0"

On windows you will have to use other tools such as WAMP.

So the Compile code on my Mac would be:

Compile.hxml


-java java
-main Graphics2DExample
-D haxe3
--next
-cmd cd java/src 
-cmd javac haxe/root/Graphics2DExample.java -d ../
-cmd cd ..
#-cmd java haxe/root/Graphics2DExample
#-cmd appletviewer index.html
-cmd open -a /Applications/firefox.app http://localhost:8080

windows or linux would vary slightly, but if in doubt, you can manually drag the html file to your browser.

The html file created in the Java folder can use the java class file directly.

Index.html


<html>
<head>
  <title>Graphics 2D Example</title>
</head>
<body>
  <h3>Graphics 2D Example</h3>
  <applet code="haxe.root.Graphics2DExample.class" width="700" height="500"
          alt="Error loading applet!">
  </applet>
</body>
</html>

But we can create the achive 'Jars' file ourself, hopefully this will reduce the download time. We create the jars from within our 'java' folder we can run
jar cvf Graphics2DExample.jar *

Then we modify the html to use the jars archive.
<html>
<head>
  <title>Graphics 2D Example</title>
</head>
<body>
  <h3>Graphics 2D Example</h3>
  <applet code="haxe.root.Graphics2DExample.class" archive="Graphics2DExample.jar" width="700" height="500"
          alt="Error loading applet!">
  </applet>
</body>
</html>

To get loaded images working we need to adjust the permissions and sign the applet as discussed earlier we use different commands for loading images in applets.
More information on working with Jars can be found on Oracles site.
http://docs.oracle.com/javase/tutorial/deployment/jar/

Font


Drawing text is very simple we just use the drawString command in our paintComponent method.
g2D.drawString("abc", 25, 25);

We can adjust the font and get the metrics.
g2D.getFontMetrics();

The font metrics are fairly standard font info, the same as how it works with hxswfml / hxformat. Need to add an image here.
getAscent()
getDescent()
getLeading()
getHeight()
charWidth()
charWidth()
getWidths()

Rotating font or distorting text is a bit more complex. The general technique is that you get the shapes for all the letters from the font definition based on the letters being rendered and our g2D text context using createGlyphVector.
 var s = "A test string";
 (new Font( "Georgia", Font.PLAIN, 24 ).createGlyphVector( g2D.getFontRenderContext(), s );

We can then grab the PathIterator for each letters shape, and iterate through it to apply an AfflineTransform, if we pass a second parameter to the PathIterator say 0.001 then Java will flatten the Path ( approximating curve data as lineTo data ).
To extract the shape path we iterate through the PathIterator and can get the currentSegment and extract coordinates and do a switch statement to calculate the draw command used ( PathIterator.SEG_CLOSE, PathIterator.SEG_MOVETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, PathIterator.SEG_MOVETO, PathIterator.SEG_QUADTO ). When the data is flattened we don't have to worry about Cubic and Quad, but we do need to know the winding. The currentSegment has a different number of points depends on the draw command.


But basic rotation we can do in a simpler way, we can create a new path and append each letters shape to it. Some useful imports.
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.Font;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;

Example of rotation amend the paintComponent method:

    @:overload public function paintComponent( g: Graphics )
    {
        
        super.paintComponent( g );
        var g2D: Graphics2D = cast g;
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON );
        g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                                RenderingHints.VALUE_RENDER_QUALITY );
        var font = new Font( "Georgia", Font.PLAIN, 24 );          
        var s = "A test string";
        g2D.setColor( Color.ORANGE );
        var frc = g2D.getFontRenderContext();
        g2D.translate( 40, 80 );
        // create all the outlines of the font
        var gv = font.createGlyphVector( frc, s );
        var l = gv.getNumGlyphs();
        var newPath = new GeneralPath( );
        // iterate through the letters and append to a new path.
        for( i in 0...l ) newPath.append( gv.getGlyphOutline( i ), false );
        // rotate the newPath
        var at2 = new AffineTransform();
        at2.rotate( Math.PI/8 );
        g2D.fill( at2.createTransformedShape( newPath ) );
        g2D.dispose();
    }

version #19636, modified 2013-08-11 06:56:07 by JLM