Copyright © 2003 International Business Machines Corp.
 Eclipse Corner Article

Graphics Context - Quick on the draw

Summary
The package org.eclipse.swt.graphics contains classes that allows management of graphics resources. Graphics can be drawn on anything that implements org.eclipse.swt.graphics.Drawable, which includes org.eclipse.swt.widgets.Control and org.eclipse.swt.graphics.Image. The class org.eclipse.swt.graphics.GC encapsulates all of the drawing API, including how to draw lines and shapes, draw text and images and fill shapes. This article shows how to use a GC to draw onto an Image, or onto a control through its paintEvent callback. The Canvas control, specifically designed for drawing operations, has a number of constructor style bits that allow you to determine when and how painting occurs, and the article shows how to use these.

By Joe Winchester, IBM
July 3, 2003



Table of contents: The SWT graphics system uses the same coordinate convention used for controls, where the origin is at 0,0 is the top left corner, and the x axis increases to the right while the y axis increases downwards.  The Point class is used to represent both a location (a position in the coordinate system) and also an offset (there is no Dimension class in SWT - the size of a rectangle is represented by a Point capturing the x and y offset from its origin).

Graphics Context

Graphics can be drawn on anything that implements org.eclipse.swt.graphics.Drawable.  This includes a Control, an Image, a Display device or a Printer device.  The class org.eclipse.swt.graphics.GC is a graphics context that encapsulates the drawing operations that can be performed.  There are two common ways to use a GC; either by creating one using the Drawable instance as a constructor argument, or else using a GC that's given to you as part of a paintEvent callback.

Drawing on an Image

The code below creates a GC with an image as its argument and draws two lines on it,  one from the top left (0,0) to the bottom right, and  one from the top right to the bottom left.

    Image image = new Image(display,"C:/devEclipse_02/eclipse/plugins/org.eclipse.platform_2.0.2/eclipse_lg.gif");
    GC gc = new GC(image);
    Rectangle bounds = image.getBounds();
gc.drawLine(0,0,bounds.width,bounds.height);
gc.drawLine(0,bounds.height,bounds.width,0);
gc.dispose();
    image.dispose();
 
 Original image  Image after the GC draws across it

When creating a GC you must be responsible for disposing it  by calling its dispose() method. For more information on how to manage SWT resources see SWT: The Standard Widget Toolkit.  A GC that's instantiated by the program should be drawn upon and then disposed as soon as possible.  This is because each GC requires an underlying platform resource, and on some operating systems these may be scarce, such as Windows 98 that only allows 5 GC objects to be created before it runs out resources.

Drawing on a Control

The class org.eclipse.swt.widgets.Control is a Drawable, so you could draw onto a Control the same way you draw onto an Image (passing the Control as the argument of GC to draw onto it), however unlike drawing on an image (that permanently changes the data making up the graphic), if you use a GC to draw onto a control you must be aware that when the operating system itself draws the control it will overwrite your changes.  The correct way to draw onto a control is by adding a listener to its paint event.  The listener class is org.eclipse.swt.events.PaintListener, and the callback method argument is an instance of org.eclipse.swt.events.PaintEvent.  The PaintEvent includes a GC that you can send message to, that is already prepared to draw onto the control and includes the damaged area.

The following code  adds a paint listener to a Shell, and in the paintControl callback method  draws a line from the origin to bottom right corner.

    Shell shell = new Shell(display);
shell.addPaintListener(new PaintListener(){
        public void paintControl(PaintEvent e){
            Rectangle clientArea = shell.getClientArea();
         e.gc.drawLine(0,0,clientArea.width,clientArea.height);
        }
    });
    shell.setSize(150,150)

Although the size of the Shell is set to (150,150), the area that can be drawn onto is smaller.  This is known as the client area, and takes into account any trim or borders; for a Shell this includes its edges, titlebar and menubar.  To determine the clientArea of any Composite use the method getClientArea().

The application always get a paint event after the underlying OS has drawn the control, so any drawing done to the paint event's GC will be shown on top of the control.  There are some exceptions to this, such as for a ToolBar where on certain platforms the items are heavyweight controls that can't be drawn on top of, however this is not considered normal behavior.  For general-purpose drawing the control org.eclipse.swt.widgets.Canvas can be used that is optimized for graphics operations.

Clipping

The clipping area of a GC is the portion onto which visible drawing occurs. By default a GC is clipped to the bounds of the drawable that it was constructed with. Altering the clipping area of a GC allows you to create graphic effects.  An example of this is if you wanted to fill a triangle shape that had a portion of its edges missing.  One way to do this would be to draw multiple polygons rectangles making up the shape, another way however is to fill the shape, but to clip the GC so that the edges are outside the clipping area.

    shell.addPaintListener(new PaintListener() {
        public void paintControl(PaintEvent e) {
            Rectangle clientArea = shell.getClientArea();
            int width = clientArea.width;
            int height = clientArea.height;
         e.gc.setClipping(20,20,width - 40, height - 40);
            e.gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
         e.gc.fillPolygon(new int[] {0,0,width,0,width/2,height});
        }
    });

This code draws a triangle on a Shell as a polygon from  the  top left, top right, and center of the bottom edge. Beforehand however, the GC is clipped with a rectangle  that reduces the client area with a margin of 20 pixels wide, so only the clipped rectangle is draw.

When a paintEvent occurs for a control the GC is always clipped to just the area that needs repainting. For example, if another window is moved in front of an SWT shell and then moved away, the newly revealed portion of the GUI is marked as damaged and a paint event is queued.  When the paint event occurs, the paintControl(PaintEvent evt) method argument contains the area of the Control that requires redrawing in its x, y, width and height fields.  The damage to the control can be complex containing multiple disjoint rectangles, and if there is more than one damaged area of a control when the paint event occurs the damaged areas are merged into a single rectangle representing the union of the damaged areas. This step is performed by the underlying platform and is designed to aid performance as multiple paint events are processed in a single callback.

In the example above that fills a clipped triangle  each time the paintControl(PaintEvent) method is called, an optimization could be to look at the PaintEvent's area.  It maybe that the paint event doesn't even intersect the shape being drawn in which case no drawing need occur, or if only a portion of the drawing is needs repainting then only this is drawn.  Depending on the type of drawing, working out which portion of a GC to selectively redraw however can be more expensive than just relying on the fact the GC is clipped, and in practice code inside paint events often just ignores the damaged area and re-does all of the GC drawing, relying on clipping to ensure only the damaged area is refreshed.

If a program needs to manually damage an area of a control this can be done using Control.redraw(int x, int y, int width, int height), or Control.redraw() to damage the entire client area.  The area is marked as damaged and will be included in the next paint event that occurs.  To cause a synchronous and immediate paint event to occur use the method Control.update() that will force all outstanding paint requests to be processed for the control. If there are no paint request  (i.e. none of the client area is damaged), then update() will do nothing.

Canvas

Although any Control can be drawn onto through its paintEvent, the subclass org.eclipse.swt.widgets.Canvas is specifically designed for graphics operations. This can be done either by using a Canvas and adding a paint listener, or through subclassing to create a re-usable Custom Control.  Canvas has a number of style bits that can be used to affect how painting occurs.

The default behavior for a Canvas is that before it paints itself the entire client area is filled with the current background color.  This can create screen flicker, because if the paintEvent also draws onto the GC, then the user sees a flash between the original background being filled, and the drawing occurring.  One way to avoid this is to use the style bit SWT.NO_BACKGROUND when creating the Canvas.  This prevents the native background from being drawn, however it means the program must be responsible for drawing every pixel of the client area.

When a widget is resized a paint event occurs.  This can create screen flicker as repeated repainting of the client area occurs.  Flicker is also known as flash, and the style bit SWT.NO_REDRAW_RESIZE can be used to reduce this.  When NO_REDRAW_RESIZE is used and the control's size is reduced no paint event is generated.  This means there will be no flash as the control is not unnecessarily redrawn, and if the size is increased then the paint event's GC is clipped set to just the area that needs repainting.  This is the newly revealed bottom and right edge rectangles in a shape like a backwards L.

The style bit NO-REDRAW_RESIZE works well to reduce flicker if a fixed-size drawing is being done on the GC, and each newly exposed area generates a paint event onto which a fresh piece of the drawing occurs.  Incorrectly used however, NO_REDRAW_RESIZE can lead to a graphic effect known as cheese.  Cheese is a general term that applies when a portion of a widget is not updated correctly following a resize. An example of  is shown below where the paint event needs to update the entire client area, such as  fill it with an oval shape.  Because no paint event occurs when the window is reduced in size the shape is not redrawn when the size is decreased, and when the window is made larger because the GC is clipped to just the damaged area Cheese occurs in the newly expanded area because the previous drawing is not erased ( as the paint event is clipped to only the newly exposed area ).

    shell.setLayout(new FillLayout());
final Canvas canvas = new Canvas(shell,SWT.NO_REDRAW_RESIZE);

    canvas.addPaintListener(new PaintListener() {
        public void paintControl(PaintEvent e) {
            Rectangle clientArea = canvas.getClientArea();
            e.gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
         e.gc.fillOval(0,0,clientArea.width,clientArea.height);
        }
    });

When the canvas size is increased the GC is clipped to just the area that needs repainting and cheese occurs.

To correct this problem in line  the style bit of SWT.NONE should be used so that the GC is not clipped on expansion, and a paint event occurs even when the shell size is reduced so the full oval can be repainted.

final Canvas canvas = new Canvas(shell,SWT.NONE);

For any SWT widget, if more than one rectangle is damaged then the platform merges these into a single damaged area that is the union of all damaged rectangles so the SWT program only processes a single paint event. The style bit NO_MERGE_PAINTS on Canvas can be used to override this behavior which means the listener will be called with a single paint event for every separate damaged rectangle.

The style bits NO_BACKGROUND, NO_REDRAW_RESIZE and NO_MERGE_PAINTS can be used with any Composite or subclass, including Canvas, Shell, or Group.  Although this is allowed by SWT ( no exceptions will be thrown ) the Composite class's Javadoc includes the following style bit warning "... their behavior is undefined if they are used with subclasses of Composite other than Canvas".  The Canvas class is therefore the preferred control to use when performing arbitrary drawing operations.

Another way to reduce flicker is to batch up the drawing that occurs on the display by double buffering.  Double buffering is a technique where the drawing occurs onto a GC that is not the one used on the paintEvent, and the contents are then copied across. To do this you can create an Image with the same size as the Canvas' client area and then draw onto this with a GC(Image);  The final image is then transferred across to the paint event's GC with a single drawImage(Image image, int x, int y) method call.  When using this technique be aware that some platforms perform native double buffering for you already, so you may in fact be triple buffering.

Drawing lines and shapes

A GC has a number of methods that allow lines to be drawn on it, either between two points, a series of interconnected points, or a pre-defined shape.  Lines are drawn in the foreground color of the GC and can be drawn in a number of styles and with different thickeness.  For a paint event the GC has the same attributes as the control that fired the event (foreground / background color and font), and the default line style is solid with a width of 1 pixel.

GC.drawLine(int x1, int y1, int x2, int y2);

Draw a line between two points on the drawable surface beginning at x1,y1 and ending at x2,y2.  The end points are included in the line, and if they are the same then a single pixel dot will be drawn.

GC.drawPolyline(int[] pointArray);

Draw a series of interconnecting lines with the int[] representing x and y positions for consecutive points.  The statement:
gc.drawPolyline(new int[] { 25,5,45,45,5,45 });
draws a line from 25,5 to 45,45 and then from 45,45 onto 5,45.

GC.drawPolygon(int[] pointArray);

Similar to drawPolyline(int[]) except that the last point is joined to the first
gc.drawPolygon(new int[] { 25,5,45,45,5,45 });
This draws the three corners of the triangle and, by joining the point at 5,45 with 25,5 completes the shape by creating a contained area

GC.drawRectangle(int x, int y, int width, int height);

Draw a rectangle with the top left corner at x,y with the specified with and height
gc.drawRectangle(5,5,90,45);
draws a rectangle with one corner at 5,5 and the opposite corner at 95,50;

When a rectangle is drawn the bottom and right edges are drawn and it is conceptually similar to lines being drawn between its four corners.  As well as passing the x,y,width and height as individual arguments the method is overloaded so you can pass in the entire Rectangle - GC.drawRectangle(Rectangle);

GC.drawRoundedRectangle(int x, int y, int width, int height, int arcWidth, int arcHeight);

A rounded rectangle differs from a standard rectangle in that its corners are rounded.  The rounding on each corner can be thought of as 1/4 of an oval, and the arcWidth and arcHeight specify the width and height of the full oval.
gc.drawRoundedRectangle(5,5,90,45,25,15); draws a rounded rectangle with a top left corner of 5,5.  The figure below shows a zoomed picture of the bottom right corner together with an imaginary oval shown with a width of 25 and a height of 15 to show how the rounded corner is created from 1/4 of the oval.

The implementation of the rounded rectangle could be achieved with 4 calls to drawArc() and 4 to drawLine(). However on some platforms such as Windows or Photon there is an optimized platform API that SWT uses.

GC.drawOval(int x, int y, int width, int height);

An oval is draw within the rectangle defined by its top left corner (x,y) and its width and height.  To draw a circle set the width and height to be the same value.
gc.drawOval(5,5,90,45);

GC.drawArc(int x, int y, int width, int height, int startAngle, int endAngle);

An arc is drawn within the area bounded by the rectangle whose top left corner is x,y and has the specified width and height.  The startAngle is on the horizontal x axis, so 0 degrees does not represent North but instead can be thought of as pointing East.  The arc is drawn anticlockwise for the number of degrees specified by the endAngle
gc.drawArc(5,5,90,45,90,200);
This draws an arc beginning at 90 degrees, which is vertical ( 0 degrees being horizontal ), and the arc continues for 200 degrees.  The arc does not stop when it reaches the endAngle of 200 degrees relative to the 0 axis, rather it stops when it has traversed 200 degrees relative to its starting position.

GC.setLineStyle(int style);

Lines can be drawn in a number of styles that are defined as static constants on the class org.eclipse.swt.SWT beginning LINE_.
 
SWT.LINE_SOLID
SWT.LINE_DOT
SWT.LINE_DASH
SWT.LINE_DASHDOT
SWT.LINE_DASHDOTDOT

GC.setLineWidth(int width);

The default width for a line is 1 pixel, however this can be changed by setting the lineWidth of the GC.
 
gc.setLineWidth(2);
gc.setLineWidth(4);

Because line styles and width affect all of the draw operations, allowing you to achieve effects such as a dotted rectangle or an oval with a thick line.

gc.setLineWidth(3);
gc.drawOval(5,5,40,40);
gc.setLineWidth(1);
gc.setLineStyle(SWT.LINE_DOT);
gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
gc.drawRectangle(60,5,60,40);


When a property such as a line width, line style or color is changed on a GC this affects all subsequent drawing operations.  The code snippet above changes the width to 3 to draw the oval, but it then changes it back to 1 before drawing the dotted blue rectangle.  Forgetting to revert values in a GC after modifying them is a common bug when doing SWT graphics programming.

Drawing text

Text can be drawn onto a GC, the glyphs are drawn in the GC's foreground color and font, and the area it occupies is drawn with the GC's background color.  To draw text you define its top left corner, width and height.  There are two sets of methods to draw text, the first set of which have Text in their method name, and will handle line delimiters and tabs and are used to implement an emulated Label. The second set of API methods have String in their method name, and no tab expansion or carriage return processing occurs, and are used for more sophisticated controls like the StyledText used by the Eclipse Java editor.

GC.drawText(String text, int x, int y);

Font font = new Font(display,"Arial",14,SWT.BOLD | SWT.ITALIC);
// ...
gc.drawText("Hello World",5,5);
gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
gc.setFont(font);
gc.drawText("Hello\tThere\nWide\tWorld",5,25);
// ...
font.dispose();

The drawText API processes the control characters \t as tab \n as a carriage return.

GC.drawString(String text, int x, int y);

Font font = new Font(display,"Arial",14,SWT.BOLD | SWT.ITALIC);
// ...
gc.drawString("Hello World",5,5);
gc.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
gc.setFont(font);
gc.drawString("Hello\tThere\nWide\tWorld",5,25);
// ...
font.dispose()


When drawString is used, the tab and carriage return characters are not processed

The size that a String occupies when it is drawn onto a GC is based on its contents and the font of the GC.  To determine the area that a String will occupy when it is drawn use the methods GC.stringExtent(String text), or GC.textExtent(String text). These return a Point whose x and y are the width and height required to render the String argument.

GC.drawText(String text, int x, int y, boolean isTransparent);

When drawText(String text, int x, int y) is used the text is drawn in the GC's current foreground color.  If you are drawing on top of an area where you want the existing background to show through, then you can set the isTransparent argument to true.  This is useful if you are drawing on top of an image and you don't want the text to obscure the image's background.

Font font = new Font(display,"Arial",12,SWT.BOLD | SWT.ITALIC);
Image image = new Image(display,"C:/devEclipse_02/eclipse/plugins/org.eclipse.platform_2.0.2/eclipse_lg.gif");
GC gc = new GC(image);
gc.drawText("Hello World",5,5);
gc.setFont(font);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawText("Hello World",5,25,true);
gc.dispose();
image.dispose();
font.dispose();


 

GC.drawText(String text, int x, int y, int flags);

The flags are a bitmask of the constants SWT.DRAW_DELIMITER, SWT.DRAW_TAB, SWT.DRAW_TRANSPARENT and SWT.DRAW_MNEMONIC.  These determine whether or not \n is processed as a carriage return, \t as a tab expansion, whether background transparency is used, and whether & is processed as a mnemonic or a literal.

gc.drawImage(image,0,0);
gc.drawText("Hello\t&There\nWide\tWorld",5,5,SWT.DRAW_TRANSPARENT);
gc.drawText("Hello\t&There\nWide\tWorld",5,25,SWT.DRAW_DELIMITER | SWT.DRAW_TAB | SWT.DRAW_MNEMONIC );


 
 

Filling shapes

Whereas lines are drawn in the GC's foreground color, shapes are filled with the GC's background color.

GC.fillPolygon(int[]);

gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.fillPolygon(new int[] { 25,5,45,45,5,45 })

GC.fillRectangle(int x, int y, int width, int height);

gc.fillRectangle(5,5,90,45);

When a rectangle is filled, the bottom and right edges are not included.  With the code above the origin point 5,5 is included in the filled blue rectangle, however the argument rectangle's bottom right corner of 95,50 (5+90 , 45+5) is outside the filled area.  The bottom right corner of the filled rectangle is at 94,49.  This is unlike the behavior for drawRectangle(5,5,90,45), where the full shape is outlined so the bottom right corner is 95,50.

To illustrate this, the code below strokes a rectangle whose corners are 5,5 and 90,45 before filling it in cyan.  To ensure that the filling of the rectangle doesn't overwrite its drawn outline, the origin is offset to 6,6.  This moves the bottom right corner of the filled rectangle, however because it has already excluded the bottom and right edges it only needs to be inset by one to not overlap with the drawn rectangle.

gc.drawRectangle(5,5,90,45);
gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
gc.fillRectangle(6,6,89,44);

GC.fillRoundedRectangle(int x, int y, int width, int height, int arcWidth, int arcHeight);

gc.fillRoundRectangle(5,5,90,45,25,15);

Like the GC.fillRectangle(...) method, the bottom and right edges are excluded from the filled shape so the bottom right corner becomes 94,49 rather than 95,50.

GC.fillOval(int x, int y, int width, int height);

gc.fillOval(5,5,90,45);

Like the other fill APIs, the bounding rectangle's bottom right corner is inset by one.

GC.fillArc(int x, int y, int widt4h., int height, int startAngle, int endAngle);

gc.fillArc(5,5,90,45,90,200);

The arguments to the fillArc(...) method are similar to the drawArc(...), with the offset from an East axis to begin filling and the number of degrees to continue the arc in an anti-clockwise direction. However unlike the drawArc(...) whose bottom right corner of the bounding rectangle is the origin offset by the width and height, fillArc(...) follows the same pattern used by the other fill methods where the bottom and right edge are excluded and the corner inset by one.

GC.fillGradientRectangle(int x, int y, int width. int height, vertical boolean);

This allows a rectangle to be filled with a gradient of coloring between the GC's foreground and background color.  The gradient can either be horizontal or vertical

gc.setBackgrouind(display,getSystemColor(SWT.COLOR_BLUE));
gc.fillGradientRectangle(5,5,90,45,false);

This creates a horizontal gradient fill starting with the foreground color (black) on the left, and finishing with the background color (blue) on the right.  As with the other fill methods, the bottom and right edges are excluded so the bottom right corner becomes inset by 1.

gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.setForeground(display.getSystemColor(SWT.COLOR_CYAN));
gc.fillGradientRectangle(5,5,90,45,true);

The vertical gradient begins at the top with the foreground color (cyan) and finishes at the bottom with the blue background color.

XOR

When drawing occurs on a GC you are altering its pixel values that make up the graphic on the Drawable surface.  If you set the GC's XOR mode to true, then what occurs is that for each pixel the red, green and blue values of the source being drawn are XOR'd with the existing red, green and blue values and the result becomes the new destination pixel.
 

shell.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
// ...
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.fillRectangle(5,5,90,45);
gc.setXORMode(true);
gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
gc.fillRectangle(20,20,50,50);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillOval(80,20,50,50);

The filled rectangle has a background color of white (255,255,255).  When this is drawn over the blue (0,0,255) the XOR'd result is yellow (255,255,0).  The portion of the rectangle drawn over the white background (255,255,255) creates an XOR result of black (0,0,0). The filled oval has a background color or red (255,0,0).  When this is drawn over blue the resulting XOR value is purple (255,0,255), and when it is drawn over white the result is cyan (0,255,255);

Drawing Images

The class org.eclipse.swt.graphics.Image represents a graphic that has been prepared ready to be displayed on a device such as a Display or Printer.  The simplest way to create an image is to load it from a recognized file format.  Supported file formats are GIF, BMP (Windows format bitmap), JPG, PNG, and in newer Eclipse releases TIFF.

Image image = new Image(display,"C:/eclipse/eclipse/plugins/org.eclipse.platform_2.0.2/eclipse_lg.gif");

GC.drawImage(Image image, int x, int y);

Every image has a size that is determined by its bounds.  For example, the size of the image eclipse_lg.gif is 115,164 and can be determined by using the method image.getBounds(). When an image is drawn it will be shown at the width and height defined by its bounds..

gc.drawImage(image,5,5);

GC.drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight);

Images can be drawn with different sizes from their original width and height, and also portions of images can be drawn.

The src coordinates are relative to image itself, so to draw the entire image use 0,0 and width and height set to the image bound's width and height.  The dst arguments represent where to draw the image on the GC, and what size to draw it at.  The original image is 115 by 164, so to stretch it to twice the width and half the height use the statement

gc.drawImage(image,0,0,115,164,5,5,230,82);

Using the src coordinates allows you to specify that you want only a portion of the image drawn.  For example, if you only want a section from the top right area of the image drawn, you can use src coordinates of 20,0 and a width and height of 95,82.  The code below draws the destination rectangle with the same width and height (95,82) as the cropped area, however a different size could be used to stretch or shrink the image portion.

gc.drawImage(image,20,0,95,82,5,5,95,82);

Other images effects can be achieved, such as transparency, animation and alpha channel blending of images.  These are outside the scope of this article, however I hope to cover these in a future article.

Conclusion

This article has shown how a GC can be used to draw lines, text, and to fill shapes.  A GC can be created by passing the drawable into its constructor, such as an Image, or for a Control by using the paintEvent callback.  The API of a GC allows lines and shapes to be drawn in its foreground color and filled in its background color.  The general purpose Canvas control is optimized to allow drawing through its paintEvent, and it has a number of constructor style bits to control when paint events occur.  GC clipping allows you to control the region of the GC that visible drawing occurs on, and when drawing different line styles can be used, and text and images can be shown.

Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.

Other company, product, and service names may be trademarks or service marks of others.