Coordinate Systems
The Java 2D API maintains two distinct coordinate spaces:
- User space – The logical coordinate system where graphical primitives are specified
- Device space – The coordinate system of the output device (screen, window, or printer)
User space represents a device-independent logical coordinate system used by your program. All geometry passed to Java 2D rendering routines is specified using user space coordinates.
With the default transformation from user space to device space, the origin of user space corresponds to the upper-left corner of the component's drawing area. The x axis increases toward the right, while the y axis increases downward. The upper-left corner of the window is located at 0,0. Coordinates are typically specified using integers, though floating-point and double precision values are also supported when needed.
Device space represents a device-dependent coordinate system that varies based on the target rendering device. While the coordinate system of windows or screens may differ significantly from that of printers, these differences remain invisible to Java programs. During rendering, necessary transformations between user space and device space occur automatically.
Java 2D Rendering
The Java 2D API provides a unified rendering model across different types of devices. At the application level, the rendering process remains identical regardless of whether the target device is a screen or printer. When a component needs to be displayed, its paint or update method gets called automatically with the appropriate Graphics context.
The Java 2D API includes the java.awt.Graphics2D class, which extends the Graphics class to provide access to enhanced graphics and rendering capabilities. These features encompass:
- Outlining any geometric primitive using stroke and fill attributes (
drawmethods) - Filling the interior of any geometric primitive using fill attributes specified by color or pattern (
fillmethods) - Rendering any text string (
drawStringmethod) - Rendering specified images (
drawImagemethod)
Additionally, the Graphics2D class supports specific shape rendering methods like drawOval and fillRect. All listed methods fall into two categories:
- Methods that draw shapes
- Methods that influence rendering
The second category uses state attributes forming the Graphics2D context for purposes including:
- Adjusting stroke width
- Modifying how strokes connect
- Setting clipping paths to restrict rendering areas
- Translating, rotating, scaling, or shearing objects during rendering
- Defining colors and patterns for shape filling
- Specifying how multiple graphic objects combine
To utilize Java 2D API functionality in applications, cast the Graphics object passed to component rendering methods into a Graphics2D object:
public void paint(Graphics graphics) {
Graphics2D g2d = (Graphics2D) graphics;
// Additional rendering code
}
The rendering context of the Graphics2D class contains several attributes:
- Stroke attributes apply to shape outlines, enabling lines to be drawn with various point sizes and dash patterns, plus endpoint and connection decorations
- Fill attributes apply to shape interiors, allowing shapes to be filled with solid colors, gradients, and patterns
- Compositing attributes are used when rendered objects overlap existing ones
- Transform attributes apply during rendering to convert objects from user space to device space coordinates, also enabling optional translation, rotation, scaling, or shearing transformations
- Clip attributes restrict rendering to areas within the region defined by a
Shapeobject's outline - Font attributes convert text strings to glyphs
- Rendering hints specify trade-off preferences between speed and quality, such as anti-aliasing preferences
When setting attributes, appropriate attribute objects are passed. To change painting attributes to cyan-green gradient fill, construct a GradientPaint object and call the setPaint method:
GradientPaint gp = new GradientPaint(0f, 0f, Color.BLUE, 0f, 30f, Color.GREEN);
g2d.setPaint(gp);
Geometric Primitives
The Java 2D API provides a useful set of standard shapes including points, lines, rectangles, arcs, ellipses, and curves. The most important package defining common geometric primitives is java.awt.geom. Arbitrary shapes can be represented by combinations of linear geometric primitives.
The Shape interface represents geometric shapes with contours and interiors. This interface provides common methods for describing and examining two-dimensional geometric objects, supporting curve segments and multiple sub-shapes. The Graphics class supports only linear segments, while the Shape interface can support curve segments.
Points
The Point2D class defines points representing positions in (x, y) coordinate space. In Java 2D API terminology, "points" differ from pixels. Points have no area, contain no color, and cannot be rendered.
Points serve to create other shapes. The Point2D class also includes methods for calculating distances between two points.
Lines
The Line2D class serves as an abstract class representing a line. Line coordinates can be retrieved as double precision values. The Line2D class includes several methods for setting line endpoints.
Straight line segments can also be created using the GeneralPath class described below.
Rectangular Shapes
Rectangle2D, RoundRectangle2D, Arc2D, and Ellipse2D primitives all derive from the RectangularShape class. This class defines methods for Shape objects that can be described by rectangular boundary boxes. The geometry of RectangularShape objects can be inferred from rectangles that completely enclose the Shape contour.
Quadratic and Cubic Curves
QuadCurve2D enables creation of quadratic parametric curve segments. Quadratic curves are defined by two endpoints and one control point.
CubicCurve2D enables creation of cubic parametric curve segments. Cubic curves are defined by two endpoints and two control points.
Arbitrary Shapes
The GeneralPath class enables construction of arbitrary shapes by specifying a sequence of positions along the shape boundary. These positions can be connected through line segments, quadratic curves, or cubic (Bezier) curves.
Areas
Using the Area class, you can perform Boolean operations like union, intersection, and difference on any two Shape objects. This technique, known as constructive area geometry, enables rapid creation of complex Shape objects without describing every line segment or curve.
Text Handling
The Java 2D API includes various text rendering capabilities, featuring methods for rendering strings and complete classes for setting font attributes and performing text layout.
For simple static text string rendering, the most direct approach uses the drawString method through the Graphics class. To specify fonts, use the setFont method of the Graphics class.
For custom text editing routines or greater control over text layout than text components provide, use Java 2D text layout classes from the java.awt.font package.
Fonts
Fonts used to represent character shapes in strings are called glyphs. Specific characters or character combinations may represent one or more glyphs. For example, á might be represented by two glyphs, while the ligature fi might be represented by one glyph.
A font can be viewed as a set of glyphs. Individual fonts may have many faces such as italic and regular. All faces in a font share similar typographic characteristics and can be identified as members of the same font family. A set of glyphs with specific styling forms a font face, and a set of font faces forms a font family. Collections of font families constitute the available fonts in the system.
When using the Java 2D API, specify fonts through Font instances. Determine available fonts by calling the static method GraphicsEnvironment.getLocalGraphicsEnvironment and querying the returned GraphicsEnvironment. The getAllFonts method returns an array of Font instances containing all available system fonts. The getAvailableFontFamilyNames method returns lists of available font families.
Text Layout
Before text display, it must be laid out so characters appear as appropriate glyphs in proper positions. Two Java 2D mechanisms manage text layout:
- The
TextLayoutclass manages text layout, highlighting, and hit detection. Features provided byTextLayouthandle most common situations, including strings with mixed fonts, mixed languages, and bidirectional text - You can create your own
GlyphVectorobjects using theFontclass, then render eachGlyphVectorobject through theGraphics2Dclass, giving complete control over text shape and position
Text Rendering Hints
The Java 2D API enables control over shape and text rendering quality using rendering hints, encapsulated by the java.awt.RenderingHints class.
When applied to text, this capability enables anti-aliasing (also called edge smoothing). For example, the KEY_TEXT_ANTIALIASING hint allows separate control of text anti-aliasing without affecting other shape anti-aliasing. More information about rendering hints appears in the Control Rendering Quality lesson.
Image Processing
In Java 2D API, images typically consist of rectangular two-dimensional pixel arrays, where each pixel represents the color at that image position, and dimensions indicate horizontal extent (width) and vertical extent (height) when displayed.
The most important image class representing such images is the java.awt.image.BufferedImage class. The Java 2D API stores these images in memory for direct access.
Applications can create BufferedImage objects directly or obtain them from external image formats like PNG or GIF.
In either case, applications can draw on images using Java 2D API graphics calls. Therefore, images aren't limited to displaying photographic images. Different objects such as line art, text, and other graphics, even other images, can be drawn onto images.
The Java 2D API allows application of image filtering operations to BufferedImage and includes several built-in filters. For example, the ConvolveOp filter can blur or sharpen images.
The resulting image can then be drawn to the screen, sent to a printer, or saved in graphic formats like PNG or GIF.
Printing
All Swing and Java 2D graphics, including composite graphics and images, can be rendered to printers using the Java 2D Printing API. This API also provides document composition capabilities for operations like changing print page order.
Rendering to printers works similarly to rendering to screens. The printing system controls when pages render, just as the drawing system controls when components draw on screens.
The Java 2D Printing API operates on a callback model where the printing system, rather than the application, controls page timing. Applications provide document information to the printing system, which determines when to image each page.
Two functions are important for supporting printing:
- Job control – Starting and managing print jobs, including displaying standard print and setup dialogs
- Pagination – Rendering each page when requested by the printing system
When imaging pages, the printing system calls the application's print method with appropriate Graphics context. To use Java 2D API functionality during printing, convert the Graphics object to Graphics2D class as when rendering to screens.
Getting Started with Graphics
The Java 2D API is powerful and complex. However, most Java 2D API usage leverages a small subset of its functionality encapsulated in the java.awt.Graphics class. This lesson covers the most common application developer needs.
Most methods in the Graphics class fall into two basic groups:
- Drawing and filling methods enabling rendering of basic shapes, text, and images
- Attribute-setting methods affecting drawing and filling appearance
Methods like setFont and setColor define how drawing and filling methods render.
Drawing methods include:
drawString– For drawing text
g.drawString("Hello", 10, 10);
drawImage– For drawing images
g.drawImage(image,
0, 0, width, height,
0, 0, imageWidth, imageHeight,
null);
drawLine,drawArc,drawRect,drawOval,drawPolygon– For drawing geometric shapes
g2d.draw(new Line2D.Double(0, 0, 30, 40));
You can select among several methods in the Graphics class based on criteria including whether you want to render images at original size in specified locations or scale them to fit given rectangles, and whether you prefer filling image transparent areas with color or keeping them transparent.
Filling methods apply to geometric shapes including fillArc, fillRect, fillOval, fillPolygon.
Remember that in 2D graphics, each point is determined by its x and y coordinates. All drawing and filling methods require this information determining where text or images should render.
For example, to draw a line, applications call code like:
java.awt.Graphics.drawLine(int x1, int y1, int x2, int y2)
In this code (x1, y1) represents the line start point, (x2, y2) represents the line end point.
Therefore, code for drawing a horizontal line looks like:
Graphics.drawLine(20, 100, 120, 100);
Working with Geometric Shapes
In previous lessons, you learned about graphics concepts including coordinate systems and graphics object creation basics. Now you'll learn detailed lessons about 2D graphics classes. This lesson shows how to draw graphics primitives and arbitrary shapes using Graphics2D class, plus how to display graphics with fancy outline and fill styles. Topics include:
Drawing Geometric Primitives
This section explains creating standard shapes like points, lines, curves, arcs, rectangles, and ellipses.
Drawing Arbitrary Shapes
This section explains drawing shapes composed of linear geometric primitives using the GeneralPath class.
Filling and Stroking
This section explains setting stroke and fill attributes controlling outline and fill styles applied to Shape objects and text.
Drawing Geometric Primitives
The Java 2D API provides several classes defining common geometric objects like points, lines, curves, and rectangles. These geometric classes belong to the java.awt.geom package.
The PathIterator interface defines methods for retrieving elements from paths.
The Shape interface provides methods for describing and inspecting geometric path objects. This interface is implemented by the GeneralPath class and other geometric classes.
All examples in this section create geometric graphics using the java.awt.geom package, then render them using the Graphics2D class. First, obtain a Graphics2D object, for example by casting the Graphics parameter of the paint() method:
public void paint(Graphics graphics) {
Graphics2D g2d = (Graphics2D) graphics;
// Additional code
}
Points
The Point class creates points representing positions in (x,y) coordinate space. Subclasses Point2D.Float and Point2D.Double provide floating-point and double precision storage for point coordinates respectively.
// Create Point2D.Double
Point2D.Double point = new Point2D.Double(xCoordinate, yCoordinate);
To create a point at coordinates 0,0, use the default constructor Point2D.Double().
Use the setLocation method to set point positions:
setLocation(double x, double y)– Sets point position with double precision valuessetLocation(Point2D p)– Sets point position using another point's coordinates
Additionally, the Point2D class has methods calculating distance between the current point and given coordinate points, or between two points.
Lines
The Line2D class represents line segments in (x, y) coordinate space. Line2D.Float and Line2D.Double subclasses specify floating-point and double precision lines. Example:
// Draw Line2D.Double
g2d.draw(new Line2D.Double(x1, y1, x2, y2));
This class includes several setLine() methods defining line endpoints.
Alternatively, specify line endpoints using the Line2D.Float class constructor:
Line2D.Float(float X1, float Y1, float X2, float Y2)Line2D.Float(Point2D p1, Point2D p2)
In the Graphics2D class, Stroke objects define line path strokes.
Curves
The java.awt.geom package enables creation of quadratic or cubic curve segments.
Quadratic Curve Segments
The QuadCurve2D class implements the Shape interface. This class represents quadratic parametric curve segments in (x, y) coordinate space. QuadCurve2D.Float and QuadCurve2D.Double subclasses specify floating-point and double precision quadratic curves.
Several setCurve methods specify curve endpoints and control points whose coordinates can be defined directly, through other point coordinates, or using given arrays.
The method setCurve(QuadCurve2D) sets a quadratic curve with the same endpoints and control points as the provided curve. Example:
// Create new QuadCurve2D.Float
QuadCurve2D quadCurve = new QuadCurve2D.Float();
// Draw QuadCurve2D.Float with set coordinates
quadCurve.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
g2d.draw(quadCurve);
Cubic Curve Segments
The CubicCurve2D class also implements the Shape interface. This class represents cubic parametric curve segments in (x, y) coordinate space. CubicCurve2D.Float and CubicCurve2D.Double subclasses specify floating-point and double precision cubic curves.
The CubicCurve2D class has similar methods for setting curves as the QuadraticCurve2D class, except with a second control point. Example:
// Create new CubicCurve2D.Double
CubicCurve2D cubicCurve = new CubicCurve2D.Double();
// Draw CubicCurve2D.Double with set coordinates
cubicCurve.setCurve(x1, y1, ctrlx1,
ctrly1, ctrlx2, ctrly2, x2, y2);
g2d.draw(cubicCurve);
Rectangles
Classes representing primitives shown in the following examples extend the RectangularShape class, which implements the Shape interface and adds its own methods.
These methods enable obtaining information about shape position and size, checking rectangular center points, and setting shape boundaries.
The Rectangle2D class represents rectangles defined by position (x, y) and dimensions (w × h). Rectangle2D.Float and Rectangle2D.Double subclasses specify floating-point and double precision rectangles. Example:
// Draw Rectangle2D.Double
g2d.draw(new Rectangle2D.Double(x, y,
rectangleWidth,
rectangleHeight));
The RoundRectangle2D class represents rectangles with rounded corners, defined by position (x, y), dimensions (w × h), and corner arc width and height. RoundRectangle2D.Float and RoundRectangle2D.Double subclasses specify floating-point and double precision rounded rectangles.
Rounded rectangles are specified by:
- Position
- Width
- Height
- Corner arc width
- Corner arc height
Use the method setRoundRect(double a, double y, double w, double h, double arcWidth, double arcHeight) to set RoundRectangle2D object position, size, and arcs. Example:
// Draw RoundRectangle2D.Double
g2d.draw(new RoundRectangle2D.Double(x, y,
rectangleWidth,
rectangleHeight,
10, 10));
Ellipses
The Ellipse2D class represents ellipses defined by bounding rectangles. Ellipse2D.Float and Ellipse2D.Double subclasses specify floating-point and double precision ellipses.
Ellipses are fully defined by position, width, and height. Example:
// Draw Ellipse2D.Double
g2d.draw(new Ellipse2D.Double(x, y,
rectangleWidth,
rectangleHeight));
Arcs
To draw parts of ellipses, use the Arc2D class. This class represents arcs defined by bounding rectangle, starting angle, angular extent, and closure type. Arc2D.Float and Arc2D.Double subclasses specify floating-point and double precision arcs.
The Arc2D class defines three arc types represented by constants in this class: OPEN, PIE, and CHORD.
Several methods set arc size and parameters:
- Directly, through coordinates
- Through provided
Point2DandDimension2D - By copying existing
Arc2D
Additionally, use the setArcByCenter method to specify arcs starting from center points, given their coordinates and radius.
// Draw Arc2D.Double
g2d.draw(new Arc2D.Double(x, y,
rectangleWidth,
rectangleHeight,
90, 135,
Arc2D.OPEN));
Drawing Arbitrary Shapes
You've learned how to draw most shapes represented in the java.awt.geom package. To create more complex geometric graphics like polygons, polylines, or stars, use another class from this package: GeneralPath.
This class implements the Shape interface, representing geometric paths composed of line segments, quadratic curves, and cubic curves. Three constructors in this class can create GeneralPath objects using default winding rule (WIND_NON_ZERO), given winding rule (WIND_NON_ZERO or WIND_EVEN_ODD), or specified initial coordinate capacity. Winding rules specify how path interiors are determined.
public void paint(Graphics graphics) {
Graphics2D g2d = (Graphics2D) graphics;
// Additional code
}
To create an empty GeneralPath instance, call new GeneralPath(), then use following methods to add line segments to shapes:
moveTo(float x, float y)– Moves path's current point to given pointlineTo(float x, float y)– Adds linear segment to current pathquadTo(float ctrlx, float ctrly, float x2, float y2)– Adds quadratic curve segment to current pathcurveTo(float ctrlx1, float ctrly1, float ctrlx2, float ctrly2, float x3, float y3)– Adds cubic curve segment to current pathclosePath()– Closes current path
The following example illustrates using GeneralPath to draw a polyline:
// Draw GeneralPath (polyline)
int xPoints[] = {0, 100, 0, 100};
int yPoints[] = {0, 50, 50, 0};
GeneralPath polyline =
new GeneralPath(GeneralPath.WIND_EVEN_ODD, xPoints.length);
polyline.moveTo(xPoints[0], yPoints[0]);
for (int index = 1; index < xPoints.length; index++) {
polyline.lineTo(xPoints[index], yPoints[index]);
};
g2d.draw(polyline);
This example ilustrates using GeneralPath to draw a polygon:
// Draw GeneralPath (polygon)
int xPolyPoints[] = {0, 100, 0, 100};
int yPolyPoints[] = {0, 50, 50, 0};
GeneralPath polygon =
new GeneralPath(GeneralPath.WIND_EVEN_ODD,
xPolyPoints.length);
polygon.moveTo(xPolyPoints[0], yPolyPoints[0]);
for (int index = 1; index < xPolyPoints.length; index++) {
polygon.lineTo(xPolyPoints[index], yPolyPoints[index]);
};
polygon.closePath();
g2d.draw(polygon);
Note that the only difference between the last two code examples is the closePath() method. This method transforms the polyline into a polygon by drawing a straight line to the previous moveTo coordinates.
To append specific paths to the end of your GeneralPath object, use one of the append() methods.
Stroking and Filling Graphics Primitives
You know how to create different geometric primitives and more complex shapes. This lesson teaches adding color and fancy outlines to graphics and represents filling and stroking:
- Filling – Process of drawing shape interiors with solid colors, color gradients, or texture patterns
- Stroking – Process of drawing shape outlines, applying stroke width, line style, and color properties
To apply fancy line styles and fill patterns to geometric primitives, change stroke and draw properties in the Graphics2D context before rendering. For example, create appropriate Stroke objects to draw dashed lines. Before rendering lines, add this stroke to the Graphics2D context by calling the setStroke method. Similarly, create GradientPaint objects and add them to the Graphics2D context to apply gradient fills to Shape objects.
The following code lines enrich geometric primitive fill and stroke contexts:
// Draw RoundRectangle2D.Double
final static float dashPattern[] = {10.0f};
final static BasicStroke dashedStroke =
new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dashPattern, 0.0f);
g2d.setStroke(dashedStroke);
g2d.draw(new RoundRectangle2D.Double(x, y,
rectangleWidth,
rectangleHeight,
10, 10));
// Fill Ellipse2D.Double
GradientPaint redToWhite = new GradientPaint(0, 0, Color.RED, 100, 0, Color.WHITE);
g2d.setPaint(redToWhite);
g2d.fill(new Ellipse2D.Double(0, 0, 100, 50));
Defining Fancy Line Styles and Fill Patterns
Use Java 2D's Stroke and Paint classes to define fancy line styles and fill patterns.
Line Styles
Line styles are defined by stroke attributes in the Graphics2D rendering context. To set stroke attributes, create a BasicStroke object and pass it to the Graphics2D setStroke method.
A BasicStroke object stores information about line width, join style, cap style, and dash style. This information is used when rendering Shape objects with draw methods.
Line width is thickness perpendicular to the line's trajectory. Line width is specified as a float value in user coordinate units, which approximately equal 1/72 inch when using default transformation.
Join style is decoration applied where two line segments meet. BasicStroke supports three join styles:
JOIN_BEVEL
JOIN_MITER
JOIN_ROUND
Cap style is decoration applied at line segment ends. BasicStroke supports three cap styles:
CAP_BUTT
CAP_ROUND
CAP_SQUARE
Dash style defines patterns of opaque and transparent sections applied along the line length. Dash styles are defined by a dash array and dash phase. The dash array defines the dash pattern. Alternating array elements represent dash lengths and space lengths between dashes in user coordinate units. Element 0 represents the first dash, element 1 represents the first space, and so forth. Dash phase is offset in the dash pattern, also specified in user coordinate units. Dash phase indicates which part of the dash pattern applies to the line's beginning.
Fill Patterns
Fill patterns are defined by the paint attribute in the Graphics2D rendering context. To set the paint attribute, create an object instance implementing the Paint interface and pass it to the Graphics2D setPaint method.
Three classes implement the Paint interface: Color, GradientPaint, and TexturePaint.
To create a GradientPaint, specify starting position and color plus ending position and color. The gradient changes from one color to another along the line connecting the two positions.
The TexturePaint class's patterns are defined by BufferedImage class. To create a TexturePaint object, specify the image containing the pattern and the rectangle used to replicate and anchor the pattern.
Using Text APIs
This lesson introduces text API concepts for applying text rendering functionality. You've already used basic Java 2D text APIs and know how to set fonts and positions and draw text.
This lesson expands that material, helping you understand how to use these APIs and learn more about Java 2D text display capabilities.
Topics include:
Physical and Logical Fonts
This section explains using Font class methods to determine available system fonts, create Font objects, and obtain font family information.
Measuring Text
This section explains properly measuring text using FontMetrics class instances.
Advanced Text Display
This section explains positioning and rendering styled text segments, displaying anti-aliased text, using text attributes to set text styles, and handling bidirectional text.
Font Concepts
This section introduces the Font class, supporting detailed font information specifications and complex typography functionality.
A Font object represents a font face instance from the collection of available system fonts. Common font face examples include Helvetica Bold and Courier Bold Italic. A Font object associates three names: its logical name, family name, and font face name:
- A
Fontobject's logical name maps to specific fonts available on the system. When specifyingFontin Java, use font face name instead of logical name. Obtain logical name fromFontby callinggetNamemethod. Calljava.awt.GraphicsEnvironment.getAvailableFontFamilyNamesmethod to get list of logical names mapping to specific available system fonts. - A
Fontobject's family name is the font family name determining typographic design across multiple font faces, such as Helvetica. Retrieve family name throughgetFamilymethod. - A
Fontobject's font face name refers to actually installed system fonts. This is the name you should use when specifying fonts, often called font name. Retrieve font name by callinggetFontName. Calljava.awt.GraphicsEnvironment.getAllFontsmethod to determine available system fonts.
Access Font information through the getAttributes method. Font object attributes include its name, size, transform, and font characteristics like weight and posture.
A LineMetrics object encapsulates measurement information related to Font, such as its ascent, descent, and leading:
- Ascent is distance from baseline to ascent line. This distance represents typical capital letter height, though some characters may extend above the ascent line.
- Descent is distance from baseline to descent line. Most character lowest points fall within the descent line, though some characters may extend below it.
- Leading is recommended distance from bottom of descent line to top of next line.
These measurements properly position characters along lines and position lines relative to each other. Access these line metrics through getAscent, getDescent, and getLeading methods. Also access information about Font object height, baseline, and underline and strikeout characteristics through the LineMetrics class.
Text Layout Concepts
Before displaying text segments, they must be properly shaped and positioned with appropriate glyphs and ligatures. This process is called text layout. Text layout involves:
- Shaping text using appropriate glyphs and ligatures
- Properly ordering text
- Measuring and positioning text
Information used to arrange text is also necessary for performing text operations like cursor positioning, hit detection, and highlighting. See Handling Bidirectional Text for more information about these text operations.
To develop deployable international market software, text must be arranged in different languages according to appropriate writing system rules.
This section covers:
- Shaping text
- Ordering text
- Measuring and positioning text
Shaping Text
Glyphs are visual representations of one or more characters. Glyph shape, size, and position depend on context. Many different glyphs can represent single characters or character combinations depending on font and style.
For example, in cursive handwriting, specific character shapes may vary depending on how they connect to adjacent characters.
In certain writing systems, especially Arabic, glyph context must always be considered. Unlike English, cursive forms are mandatory in Arabic; Arabic text cannot be rendered without cursive forms.
These cursive forms may vary significantly in shape depending on context. For example, the Arabic letter heh has four cursive forms shown in the following figure:
Despite these four forms being very different from each other, this cursive form variation is fundamentally no different from cursive writing in English.
Sometimes two glyphs' shapes can change even more dramatically, merging into a single glyph. Such merged glyphs are called ligatures. For example, most English fonts contain the following fi ligature shown in the figure:
Merged glyphs consider the overhang of the letter f and combine characters in a natural way rather than simply letting letters collide.
Ligatures are also used in Arabic, and some ligature usage is mandatory; rendering certain character combinations with out appropriate ligatures is unacceptable. When forming ligatures from Arabic characters, shapes change even more fundamentally than in English. The following figure illustrates how two Arabic characters combine into a single ligature when placed together.
Text Ordering
In Java programming language, text is encoded using Unicode character encoding. Unicode-encoded text is stored in memory in logical order. Logical order is the sequence in which characters and words are read and written. Logical order doesn't necessarily match visual order, which is the sequence in which corresponding glyphs display.
Visual order of glyphs in specific writing systems (scripts) is called script order. For example, Roman script order goes from left to right, while Arabic and Hebrew script order goes from right to left.
Some writing systems have additional rules beyond script order for arranging glyphs and words on text lines. For example, Arabic and Hebrew numbers are arranged from left to right even though letters go from right to left. This means Arabic and Hebrew are truly bidirectional text even without embedded English text. See Handling Bidirectional Text for more information.
Measuring and Positioning Text
Unless you use fixed-width fonts, different characters in fonts have different widths. This means all text positioning and measurement must account for exactly which characters are used, not just quantity. For example, to right-align displayed number columns in proportional fonts, you cannot simply use extra spaces to position text. To properly align columns, you need to know each number's exact width to adjust accordingly.
Text is usually displayed using multiple fonts and styles like bold or italic. In such cases, even identical characters may have different shapes and widths depending on their style. To properly position, measure, and render text, you need to track each individual character and style applied to that character. Fortunately, the TextLayout class handles this work for you.
To properly display text in languages like Hebrew and Arabic, you must measure and position each individual character and place it in adjacent character context. Since character shape and position may vary based on context, measuring and positioning such text without considering context produces unacceptable results.
Additionally, Java SE provides the FontMetrics class, enabling you to obtain measurements of text rendered by Font objects, such as text line height in a font. You can use this information to precisely position text in Java graphics applications. See Measuring Text for more information.
Physical and Logical Fonts
Two font types exist: physical fonts and logical fonts. Physical fonts are actual font libraries, including TrueType or PostScript Type 1 fonts. Physical fonts can be Times, Helvetica, Courier, or any other fonts, including international fonts. Logical fonts are five font families: Serif, SansSerif, Monospaced, Dialog, and DialogInput. These logical fonts aren't actual font libraries. Instead, Java runtime environment maps them to physical fonts through logical font names.
This section helps you determine which fonts to use in applications. Topics include:
Physical Fonts
Physical fonts are actual font libraries containing glyph data and tables using font technologies like TrueType or PostScript Type 1 to map character sequences to glyph sequences. To obtain names of all available font families installed in the system, call:
GraphicsEnvironment graphicsEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontFamilyNames = graphicsEnv.getAvailableFontFamilyNames();
The FontSelector sample program demonstrates how to locate and select these fonts.
Lucida Fonts
Oracle's JRE includes this physical font series, also licensed for other Java platform implementations. These fonts are physical fonts but don't depend on host operating systems.
Applications using these fonts can achieve identical appearance wherever these fonts are available. Additionally, these fonts cover extensive languages (especially European and Middle Eastern), allowing creation of fully multilingual applications for supported languages. However, these fonts may not be available in all JREs. Furthermore, they currently don't cover complete Unicode character sets; particularly, Chinese, Japanese, and Korean aren't supported.
Bundling Physical Fonts with Your Application
Sometimes applications can't rely on system-installed fonts, usually because the font is unavailable custom font. In such cases, fonts must be bundled with applications.
Use one of following methods to create Font objects from existing physical fonts:
Font java.awt.Font.createFont(int fontFormat, InputStream inputStream);
Font java.awt.Font.createFont(int fontFormat, File fontFile);
To create Font objects from TrueType fonts, format parameter fontFormat must be constant Font.TRUETYPE_FONT. Following example creates Font object from TrueType font file A.ttf:
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
Direct file access is simpler and more convenient. However, if your code can't access file system resources, or fonts are packaged in Java Archive (JAR) files with rest of application or applet, you may need InputStream objects.
The createFont method creates new Font objects with point size 1 and style PLAIN. Then use Font.deriveFont method to derive new Font objects with different sizes, styles, transforms, and font characteristics from this base font. Example:
try {
// Returned font is of pt size 1
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
// Derive and return a 12 pt version:
// Need to use float otherwise
// it would be interpreted as style
return font.deriveFont(12f);
} catch (IOException | FontFormatException e) {
// Handle exception
}
Using deriveFont method is important because fonts created by applications don't belong to font sets known by underlying font system. Since deriveFont method works from originally created font, it doesn't have this limitation.
An alternative solution registers created fonts to graphics environments. Example:
try {
GraphicsEnvironment graphicsEnv =
GraphicsEnvironment.getLocalGraphicsEnvironment();
graphicsEnv.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")));
} catch (IOException | FontFormatException e) {
// Handle exception
}
After registering fonts to graphics environment, fonts become available when calling getAvailableFontFamilyNames() and can be used in font constructors.
Logical Fonts
Java SE defines five logical font families:
DialogDialogInputMonospacedSerifSansSerif
These fonts are available on any Java platform and can be viewed as aliases for certain base fonts with attributes suggested by their names. Serif fonts resemble Times New Roman and are typically used for printing. Sans Serif fonts are better suited for screen use.
These fonts can be customized according to user locale. Additionally, these fonts support widest code point (Unicode character) ranges.
Besides font families, fonts have other attributes, most importantly style and size. Styles include Bold and Italic.
Java 2D API's default font is 12-point Dialog. This font is typical size for reading text on normal 72-120 DPI display devices. Applications can directly create instances of this font by specifying:
Font font = new Font("Dialog", Font.PLAIN, 12);
Advantages and Disadvantages of Using Physical and Logical Fonts
Physical fonts enable applications to fully leverage all available fonts, achieving different text appearances and maximum language coverage. However, creating applications using physical fonts is much more difficult.
Bundling physical fonts with your application enables creation of applications looking identical everywhere and gives complete control over application support. However, bundled fonts can be large, especially if you want your application to support Chinese, Japanese, and Korean. Additionally, you may need to address licensing issues.
Logical font names guarantee working anywhere and can at least render text in host operating system localized languages (usually supporting wider language ranges). However, physical fonts used to render text vary between implementations, host operating systems, and locales, so applications can't achieve identical appearance everywhere. Additionally, mapping mechanisms sometimes limit renderable character ranges. This was a major problem before JRE version 5.0: for example, Japanese text could only be rendered on Japanese localized host operating systems, couldn't be rendered on other localized systems even with Japanese fonts installed. For applications using 2D font rendering, this problem is much less significant in JRE versions 5.0 and later because mapping mechanisms now typically recognize and use fonts supporting all supported writing systems (if installed).
Measuring Text
To properly measure text, you need to learn some methods and avoid some mistakes. Font metrics are measurements of text rendered by Font objects, such as text line height in a font. Most common method for measuring text uses FontMetrics instances encapsulating this metric information. Example:
// Get metrics from the graphics
FontMetrics metrics = graphics.getFontMetrics(font);
// Get the height of a line of text in this
// font and render context
int height = metrics.getHeight();
// Get the advance of my text in this font
// and render context
int advance = metrics.stringWidth(text);
// Calculate the size of a box to hold the
// text with some padding.
Dimension size = new Dimension(advance + 2, height + 2);
For many applications, this approach suffices for evenly spaced text lines or Swing component sizing.
Consider following:
- These metrics are obtained from
Graphicsclass because this class encapsulatesFontRenderContext, which is required for accurate text measurement. At screen resolution, fonts are adjusted for readability. As text size increases, this adjustment isn't linearly scaled. Therefore, at 20 pt, font-displayed text length won't be exactly twice that at 10 pt. Besides text itself and font, another important information for measuring text isFontRenderContext. This method includes transformation from user space to device pixels for text measurement. - Height reports without reference to specific text strings. For example, in text editors, this is useful when you want identical line spacing between each text line.
stringWidth()returns text advance width. Advance width is distance from text origin to subsequently rendered string position.
When using these methods to measure text, note that text can extend in any direction beyond rectangular bounds defined by font height and string advance.
Usually, simplest solution ensures text isn't clipped, for example, by surrounding components. Add padding in situations potentially causing text clipping.
If this solution isn't sufficient, other text measurement APIs in Java 2D software can return rectangular bounding boxes. These boxes consider specific text height and pixelation effects for measurement.
Advanced Text Display
Java 2D API provides mechanisms supporting complex text layout. This section describes following advanced text display features.
Displaying Anti-Aliased Text Using Rendering Hints
This section explains how to control rendering quality using rendering hints.
Setting Text Style Using Text Attributes
This section explains using TextAttribute class to add underlines or strikeouts to text.
Drawing Multi-Line Text
This section explains using TextLayout and LineBreakMeasurer classes to position and render styled text segments.
Handling Bidirectional Text
This section discusses handling bidirectional text using classes from java.awt and java.awt.font packages.
Displaying Anti-Aliased Text Using Rendering Hints
Java 2D text rendering may be affected by rendering hints.
Remember the most important text drawing method:
Graphics.drawString(String text, int x, int y);
Typically, this method draws each glyph in text string using solid color, where each "on" pixel in the glyph is set to that color. This drawing approach produces highest contrast text but sometimes creates jagged (stair-step) edges.
Text anti-aliasing is technique for smoothing text edges on screens. Java 2D API enables applications to specify whether this technique should be used and which algorithm to use by applying text rendering hints to Graphics.
Most common rendering hint blends foreground (text) color with screen background pixels at text edges. To request this hint, applications must call following method:
graphics2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Following illustration demonstrates anti-aliasing feature.
Improper use may make text appear overly blurry. In such cases, better hint is following:
graphics2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
This approach automatically uses font's own information to decide whether to use anti-aliasing or solid color.
LCD displays have properties Java 2D API can exploit to produce text unlike typical anti-aliasing (not as blurry) but more readable at small sizes. To request drawing text using typical LCD display sub-pixel LCD text mode, applications must call following method:
graphics2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
Setting Text Style Using Text Attributes
Applications often need to apply following text attributes:
- Underline – Line drawn beneath text
- Strikeout – Horizontal line drawn through text
- Superscript or subscript – Text or letters slightly above or correspondingly below a line
- Character spacing – Adjusting space between characters
These and other text attributes can be applied using Java 2D TextAttribute class.
To apply these text attributes, add them to Font objects. Example:
Map<TextAttribute, Object> attributeMap =
new Hashtable<TextAttribute, Object>();
attributeMap.put(TextAttribute.KERNING,
TextAttribute.KERNING_ON);
font = font.deriveFont(attributeMap);
graphics.setFont(font);
Drawing Multi-Line Text
If you have styled text segment you want to fit specific width, you can use LineBreakMeasurer class. This class enables styled text to be broken into lines so they fit specific visual advance. Each line returns as TextLayout object representing immutable, styled character data. However, this class also enables access to layout information. TextLayout's getAscent and getDescent methods return information about fonts used for positioning lines in components. Text is stored as AttributedCharacterIterator object so font and point size attributes can be stored with text.
Following applet uses LineBreakMeasurer, TextLayout, and AttributedCharacterIterator to position styled text segment in component.
The following code creates iterator containing string paragraphText. It retrieves iterator start and end, then creates new LineBreakMeasurer from iterator.
AttributedCharacterIterator paragraph = paragraphText.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2d.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph, frc);
Window size determines where lines should break. A TextLayout object is created for each line in paragraph.
// Set break width to width of Component.
float breakWidth = (float)getSize().width;
float drawPosY = 0;
// Set position to the index of the first
// character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines from until the entire paragraph
// has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph
// is right-to-left we will align the
// TextLayouts to the right edge of the panel.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the
// layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX,drawPosY).
layout.draw(g2d, drawPosX, drawPosY);
// Move y-coordinate in preparation for next
// layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
TextLayout class typically isn't created directly by applications. However, when applications need to handle text with styling (text attributes) applied at specific text positions directly, this class becomes very useful. For example, to italicize a word in paragraph, applications need to measure each substring and set fonts. If text is bidirectional, correctly completing this task isn't easy. Handle this by creating TextLayout object from AttributedString object. Refer to Java SE specification for more information about TextLayout.
Handling Bidirectional Text
This section discusses handling bidirectional text using classes from java.awt and java.awt.font packages. These classes allow drawing styled text in any language or script supported by Unicode standard: global character encoding system for handling various modern, classical, and historical languages. When drawing text, text reading direction must be considered so all words in string display correctly. These classes maintain text direction and draw it correctly regardless of whether string runs left-to-right, right-to-left, or bidirectionally. Bidirectional text presents interesting questions for properly positioning cursors, accurately positioning selections, and correctly displaying multi-line text. Additionally, bidirectional and right-to-left text present similar problems for correctly moving cursors according to right arrow and left arrow key presses.
Topics include:
- Text ordering
- Manipulating bidirectional text
- Displaying cursor
- Moving cursor
- Click testing
- Highlighting selection
- Performing text layout in Java applications
- Managing text layout using TextLayout class
- Using TextLayout class for text layout
- Using TextLayout class to display dual cursors
- Using TextLayout class to move cursors
- Using TextLayout class for click testing
- Using TextLayout class to highlight selections
Text Ordering
Java SE stores text in memory in logical order, which is sequence in which characters and words are read and written. Logical order doesn't necessarily match visual order, which is sequence in which corresponding glyphs display.
Even when mixing multiple languages, bidirectional text must maintain writing system visual order. Following figure shows Arabic phrase embedded in English sentence.
Despite being part of English sentence, Arabic words display from right to left in Arabic writing order. Because italicized Arabic word logically comes after plain-text Arabic word, it visually appears to left of plain text.
When displaying mixed left-to-right and right-to-left text lines, base direction is important. Base direction is writing order of primary writing system. For example, if text is primarily English with some embedded Arabic, base direction is left-to-right. If text is primarily Arabic with some embedded English or numbers, base direction is right-to-left.
Base direction determines display order of text segments having common direction. In example shown in previous figure, base direction is left-to-right. This example has three directional runs: English text from left-to-right at sentence beginning, Arabic text from right-to-left, and period from left-to-right.
Graphics are typically embedded in text flow. These inline graphics behave similarly to glyphs in terms of affecting text flow and line breaks. Such inline graphics need to be positioned using same bidirectional layout algorithms so they appear in appropriate character stream positions.
Java SE uses Unicode Bidirectional Algorithm, algorithm for ordering glyphs within line to determine bidirectional text directionality. In most cases, you don't need to include additional information for algorithm to obtain correct display order.
Manipulating Bidirectional Text
To allow users to edit bidirectional text, you must be able to perform following operations:
- Display cursor
- Move cursor
- Click testing
- Highlight selection
Displaying Cursor
In editable text, cursor graphically represents current insertion point, where new text characters will be inserted. Typically, cursor displays as blinking vertical bar between two glyphs. New characters are inserted and displayed at cursor position.
Calculating cursor position can be complex, especially for bidirectional text. At direction boundaries, insertion offset has two possible curser positions because corresponding character offsets' two glyphs don't display adjacently. As shown in following figure. In this figure, cursor displays as brackets indicating glyph corresponding to cursor.
Character offset 8 corresponds to position after underscore and before A. If user enters Arabic character, its glyph displays to right of A; if user enters English character, its glyph displays to right of underscore.
To handle this situation, some systems display dual cursors: strong (primary) cursor and weak (secondary) cursor. Strong cursor indicates where inserted character will display when character direction matches text base direction. Weak cursor shows where inserted character will display when character direction is opposite to base direction. TextLayout automatically supports dual cursors.
When handling bidirectional text, you can't simply sum character offset's previous glyph widths to calculate cursor position. If you do, cursor will be drawn in wrong position as shown in following figure:
To properly position cursor, sum left-side glyph widths considering current context. Unless context is considered, glyph metrics may not match display. (Context may affect which glyphs are used.)
Moving Cursor
All text editors allow users to move cursor using arrow keys. Users expect cursor to move in direction of pressed arrow key. In left-to-right text, moving insertion offset is simple: right arrow key increases insertion offset by one, left arrow key decreases it by one. In bidirectional text or text with ligatures, this behavior causes cursor to span directional boundaries' glyphs and reverse movement within different directional runs.
To smoothly move cursor in bidirectional text, consider text run direction. When right arrow key is pressed, insertion offset can't simply be increased, and when left arrow key is pressed, it can't simply be decreased. If current insertion offset is within right-to-left character run, right arrow key should decrease insertion offset, and left arrow key should increase it.
Moving cursor across directional boundaries is more complex. Following figure illustrates what happens when users navigate using arrow keys when crossing directional boundaries. Moving three positions right in displayed text corresponds to moving to character offsets 7, 19, then 18.
Between certain glyphs cursor should never appear; instead, cursor should move as if these glyphs represent single character. For example, if o and diacritical mark are represented by two separate characters, cursor should never appear between them.
TextLayout class provides methods (getNextRightHit and getNextLeftHit) enabling smooth cursor movement in bidirectional text.
Hit Testing
Typically, device space position must be converted to text offset. For example, when user clicks mouse on selectable text, mouse position converts to text offset and serves as one end of selection range. Logically, this is inverse of placing cursor.
When handling bidirectional text, single visual position in display can correspond to two different offsets in source text, as shown in following figure:
Because single visual position can correspond to two different offsets, hit testing bidirectional text isn't simply measuring glyph widths until finding correct position's glyph, then mapping that position back to character offset. Detecting which side of hit position helps distinguish these two choices.
Use TextLayout.hitTestChar for hit testing. Hit information is encapsulated in TextHitInfo object and includes information about which side of hit position.
Highlighting Selection
Selected character range is graphically represented by highlight region where glyphs display in reverse color or different background color.
Highlight region, like cursor, is more complex in bidirectional text than unidirectional text. In bidirectional text, continuous character range may not have continuous highlight region when displayed. Conversely, highlight region appearing as visually continuous series of glyphs may not correspond to single, continuous character range.
This leads to two strategies for highlighting selections in bidirectional text:
- Logical highlighting: With logical highlighting, selected characters remain continuously in text model while highlight regions are allowed to be discontinuous. Here's logical highlighting example:
- Visual highlighting: With visual highlighting, there may be multiple selected character ranges, but highlight regions remain continuously. Here's visual highlighting example:
Logical highlighting is easier to implement because selected characters remain continuously in text.
Image Handling
As you already know from Image tutorial, Image is described by width and height in pixels and has coordinate system independent of drawing surface.
Many common tasks exist when handling images.
- Loading external GIF, PNG, JPEG image format files into internal image representation used by Java 2D
- Creating Java 2D images directly and rendering them
- Drawing Java 2D image contents to drawing surface
- Saving Java 2D image contents to external GIF, PNG, or JPEG image files
This lesson teaches fundamentals of loading, displaying, and saving images.
Two main classes you must know for handling images:
java.awt.Imageclass is superclass representing rectangular arrays of pixels for graphical imagesjava.awt.image.BufferedImageclass extendsImageclass, allowing applications to directly manipulate image data (for example, retrieve or set pixel colors). Applications can directly construct instances of this class.
BufferedImage class is cornerstone of Java 2D immediate mode imaging API. It manages images in memory and provides methods for storing, interpreting, and retrieving pixel data. Since BufferedImage is subclass of Image, it can be rendered through Graphics and Graphics2D methods accepting Image parameters.
BufferedImage essentially is Image with accessible data buffer. Therefore, direct use of BufferedImage is more efficient. BufferedImage has ColorModel and Raster for image data. ColorModel provides color interpretation of image pixel data.
Raster performs following functions:
- Represents image's rectangular coordinates
- Maintains image data in memory
- Provides mechanism for creating multiple sub-images from single image data buffer
- Provides methods for accessing specific pixels within image
Basic image operations are represented in following sections:
Reading/Loading Images
This section explains using Image I/O API to load images from external image formats into Java applications.
Drawing Images
This section teaches using Graphics and Graphics2D classes' drawImage methods to display images.
Creating and Drawing to Images
This section describes creating images and using image itself as drawing surface.
Writing/Saving Images
This section explains saving created images in appropriate formats.
Reading/Loading Images
When thinking of digital images, you might think of sampled image formats like JPEG image format used in digital photography or GIF images commonly used on web pages. All programs using these images must first convert them from external formats to internal formats.
Java 2D supports loading these external image formats into its BufferedImage format using Image I/O API in javax.imageio package. Image I/O has built-in support for GIF, PNG, JPEG, BMP, and WBMP. Image I/O is also extensible, so developers or administrators can "plug in" support for other formats. For example, TIFF and JPEG 2000 plugins are separately available.
To load image from specific file, use following code from LoadImageApp.java:
BufferedImage bufferedImg = null;
try {
bufferedImg = ImageIO.read(new File("strawberry.jpg"));
} catch (IOException e) {
}
Image I/O identifies file contents as JPEG format image and decodes it to BufferedImage directly usable by Java 2D.
LoadImageApp.java demonstrates how to display this image.
If code runs in applet, getting images from applet codebase is straightforward. Following excerpt from LoadImageApplet.java:
try {
URL imageUrl = new URL(getCodeBase(), "examples/strawberry.jpg");
bufferedImg = ImageIO.read(imageUrl);
} catch (IOException e) {
}
Method getCodeBase in this example returns URL of directory containing this applet when deployed on web server. If applet is locally deployed, getCodeBase returns null and applet won't run.
Following example demonstrates using getCodeBase method to load strawberry.jpg file.
LoadImageApplet.java contains complete code for this example, and this applet requires strawberry.jpg image file.
Besides reading from files or URLs, Image I/O can read from other sources like InputStream. ImageIO.read() is most direct convenience API for most applications, but javax.imageio.ImageIO class provides more static methods for advanced Image I/O API usage. Method set on this class represents only part of rich API set for discovering image information and controlling image decoding (reading) process.
We'll further explore Image I/O's other capabilities in Writing/Saving Images section.
Drawing Images
As you already know, Graphics.drawImage method draws images at specific positions:
boolean Graphics.drawImage(Image img,
int x, int y,
ImageObserver observer);
x,y position specifies image's upper-left corner location. observer parameter notifies application of asynchronously loaded image updates. observer parameter is typically not used directly and is usually null for BufferedImage class.
Described method only applies when entire image is drawn, mapping image pixels to user space coordinates 1:1. Sometimes applications need to draw image parts (sub-images), or scale images to cover specific drawing surface areas, or transform or filter images before drawing.
Overloaded drawImage() methods perform these operations. For example, following overloaded drawImage() method enables drawing as much of specified image's specified area as possible, scaling it to fit target drawable surface's specified area:
boolean Graphics.drawImage(Image img,
int destX1, int destY1, int destX2, int destY2,
int srcX1, int srcY1, int srcX2, int srcY2,
ImageObserver observer);
src parameters represent image area to copy and draw. dest parameters show target area to be covered by source area. destX1, destY1 coordinates define image drawing position. Target area's width and height dimensions are calculated by following expressions: (destX2-destX1), (destY2-destY1). If source area and target area dimensions differ, Java 2D API will scale up or down as needed.
Image Filtering
Besides copying and scaling images, Java 2D API can filter images. Filtering draws or generates new images by applying algorithms to source image pixels. Image filters can be applied using following method:
void Graphics2D.drawImage(BufferedImage img,
BufferedImageOp operation,
int x, int y)
BufferedImageOp parameter implements filter. Following applet represents image drawn over text. Drag slider to show more or less text through image and make image more transparent.
Following code shows how to perform filtering operation on BufferedImage object with alpha channel using RescaleOp object and re-scale alpha channel through this object. Alpha channel determines each pixel's transparency. It also specifies degree of coverage this image has.
/* Create an ARGB BufferedImage */
BufferedImage img = ImageIO.read(imageSource);
int width = img.getWidth(null);
int height = img.getHeight(null);
BufferedImage bi = new
BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bi.getGraphics();
graphics.drawImage(img, 0, 0, null);
/*
* Create a rescale filter op that makes the image
* 50% opaque.
*/
float[] scales = { 1f, 1f, 1f, 0.5f };
float[] offsets = new float[4];
RescaleOp rescaleOperation = new RescaleOp(scales, offsets, null);
/* Draw the image, applying the filter */
g2d.drawImage(bi, rescaleOperation, 0, 0);
Complete example in SeeThroughImageApplet.java contains code using slider to adjust transparency from initial 50%. This example also requires duke_skateboard.jpg image.
RescaleOp object is just one of many filters that can be created. Java 2D API has several built-in filters, including following:
ConvolveOp. Each output pixel is calculated from surrounding pixels in source image. Can be used to blur or sharpen images.AffineTransformOp. This filter maps pixels from source to different positions in target by applying transformation to pixel positions.LookupOp. This filter remaps pixel colors using application-provided lookup table.RescaleOp. This filter multiplies colors by certain factor. Can be used to brighten or darken images, increase or decrease opacity, etc.
Creating and Drawing Images
We already know how to load existing images created and stored in your system or at any network location. However, you might also want to create new image as pixel data buffer.
In such cases, manually create BufferedImage object using this class's three constructors:
new BufferedImage(width, height, type)- ConstructsBufferedImagewith predefined image type.new BufferedImage(width, height, type, colorModel)- ConstructsBufferedImagewith predefined image type:TYPE_BYTE_BINARYorTYPE_BYTE_INDEXED.new BufferedImage(colorModel, raster, premultiplied, properties)- Constructs newBufferedImageusing specifiedColorModelandRaster.
On other hand, we can use Component class methods. These methods analyze display resolution of given Component or GraphicsConfiguration and create appropriately typed images.
Component.createImage(width, height)GraphicsConfiguration.createCompatibleImage(width, height)GraphicsConfiguration.createCompatibleImage(width, height, transparency)
GraphicsConfiguration returns BufferedImage-type object, but Component returns Image-type object, so if you need BufferedImage object, you can perform instanceof check in code and cast to BufferedImage.
As mentioned in previous lessons, we can render images not only on screens. Images themselves can be viewed as drawing surfaces. Use BufferedImage class's createGraphics() method to achieve this purpose:
BufferedImage offscreenImage =
new BufferedImage(100, 50,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = offscreenImage.createGraphics();
Another interesting off-screen image use is automatic double buffering. This function avoids animation image flickering by drawing images to back buffer, then copying that buffer to screen instead of drawing directly to screen.
Java 2D also allows access to hardware acceleration for off-screen images, providing better rendering performance and copying from these images. You can gain benefits of this function by using following methods from Image class:
getCapabilitiesmethod allows you to determine whether image is currently accelerated.setAccelerationPrioritymethod allows you to set hints about importance of image acceleration.getAccelerationPrioritymethod gets hints about acceleration importance.
Writing/Saving Images
This lesson starts explaining how to use javax.imageio package to load images from external image formats to internal BufferedImage format used by Java 2D. Then explains using Graphics.drawImage() to draw that image, optionally filtering it.
Final stage saves BufferedImage object in external image format. This might be image originally loaded from external image format by Image I/O classes and possibly modified using Java 2D API, or image created by Java 2D.
Image I/O classes provide simple method for saving images in various image formats as shown in following example:
static boolean ImageIO.write(RenderedImage image,
String formatName,
File outputFile) throws IOException
Note: BufferedImage class implements RenderedImage interface.
formatName parameter selects image format to save BufferedImage.
try {
// Retrieve image
BufferedImage bufferedImage = getMyImage();
File outputfile = new File("saved.png");
ImageIO.write(bufferedImage, "png", outputfile);
} catch (IOException e) {
// Handle exception
}
ImageIO.write method calls code implementing PNG writing, the "PNG writing plugin". Term plugin applies to Image I/O being extensible and able to support various formats.
However, following standard image format plugins always exist: JPEG, PNG, GIF, BMP, and WBMP.
Each image format has advantages and disadvantages:
| Format | Advantages | Disadvantages |
|---|---|---|
| GIF | Supports animation and transparent pixels | Supports only 256 colors and doesn't support semi-transparency |
| PNG | Better choice than GIF or JPG for high-color lossless images, supports semi-transparency | Doesn't support animation |
| JPG | Suitable for photographic images | High compression losses, unsuitable for text, screenshots, or any applications requiring complete preservation of original image |
For most applications, using these standard plugins suffices. Their advantages are easy availability. Image I/O classes provide ways to insert support for additional formats, and many such plugins exist. If you want to know file formats loadable or savable in system, you can use ImageIO class's getReaderFormatNames and getWriterFormatNames methods. These methods return string arrays listing all formats supported by this JRE.
String writerNames[] = ImageIO.getWriterFormatNames();
Returned name array will include any installed additional plugins, and any of these names can be used as format name to select image writer. Following code example is simplified version of complete image editing/processing program using revised version of ImageDrawingApplet.java example program that can be used as follows:
- Image first loads through Image I/O
- User selects filter from dropdown list, then draws new updated image
- User selects save format from dropdown list
- File selector then appears, user selects image save location
- Modified image can now be viewed by other desktop applications
Complete code for this example appears in SaveImage.java.
In this lesson, you only learned Image I/O basics, which provide extensive support including direct use of ImageWriter plugins for finer control over encoding process. ImageIO can write multiple images, image metadata, and determine quality versus size trade-offs. More information appears in Java Image I/O API Guide.
Printing
Since Java 2D API enables drawing on any surface, its natural extension is ability to print Java 2D graphics. Printers can be viewed as graphic devices similar to displays.
Java 2D printing API isn't limited to printing graphics. It also enables printing application user interface contents. Content can be printed by sending raw data to printer and formatting it under Java 2D printing API's control, or using Java 2D graphics API to print.
In this lesson, you'll explore Java 2D printing API's printer and job control functions, which complement rendering elements. You'll learn how to find printers configured on system or network, discover information about these printers such as supported paper sizes, and select these properties for printing and user dialogs.
Main classes and interfaces participating in printing appear in java.awt.print and javax.print packages (latter package allows access to print services).
Basic printing operations are represented in following sections:
- Basic printing program – This section describes
Printableinterface and presents basic printing program - Using print setup dialog – This section explains displaying print setup dialog
- Printing multi-page documents – This section explains using pagination to print multi-page documents
- Using print services and attributes – This section teaches about print services, how to specify print data format, and how to use
javax.printpackage to create print jobs - Printing user interface contents – This section explains printing window or frame contents
- Print support in Swing components – This section briefly describes relevant printing functionality in
Swingand references specificSwingclasses and interfaces