Sun Java Solaris Communities My SDN Account Join SDN
 
Java 2D

FAQs

 
API Specifications
Documentation

FAQs
Code Samples & Apps
Technical Articles & Tips
White Papers
 
User Groups
Books & Authors
Bug Database
Forums
 
Tutorials & Code Camps
Online Sessions & Courses
Instructor-Led Courses
Course Certification
 
 
Java 2D - Frequently Asked Questions (FAQ)

General questions

Q: What is Java 2D?

A: Java 2D is an integral part of the Java SE platform that provides a powerful, flexible framework for device- and resolution-independent 2D graphics.

Java 2D handles arbitrary shapes, text, and images and provides a uniform mechanism for performing transformations, such as rotation and scaling, on these objects. With Java 2D, application developers can render to display devices, discover and render to printer devices, read and write in many image formats. There is comprehensive text and font handling and color space support and various rendering techniques and effects are offered.

Q: Who needs Java 2D?

A: The Java 2D API benefits virtually all desktop developers as it provides the rendering engine for the Swing toolkit and is also used to develop games, and graphics and imaging applications. Java 2D is of benefit to any developer who wants to create a graphically rich end-user experience.

Q: Where can I go for more information about Java 2D?

A: A good place to start is The 2D Graphics Tutorial. You can also refer to the The Java 2D Programmer's Guide

Q: Are there any news groups or mailing lists dedicated to Java 2D?

A: JAVA2D-INTEREST@JAVA.SUN.COM is the primary mailing list for those interested in Java2D. All the Java 2D engineers at Sun monitor this alias. The archives for this list can be located at Java2D-interest list archive

This mailing list is also linked to the Java 2D forum at java.net: http://forums.java.net/jive/forum.jspa?forumID=69

A few Sun engineers also participate in the Java 2D forums at javagaming.org, which is a great resource for game developers and other performance hungry individuals: http://www.javagaming.org/forums/index.php?board=15.0


Programming, Debugging and Performance questions

Q: What's the difference between Graphics and Graphics2D?

A: The Graphics class has been around since the first release of Java and supports the more basic rendering operations which are sufficient for many applications. Graphics2D is a subclass of Graphics and adds all the Java 2D API features first introduced in Java 2 in 1998.

Q: Is there a list of the various runtime flags that Java2D uses?

A: Yes! Check out: http://java.sun.com/j2se/1.5.0/docs/guide/2d/flags.html

Q: What kinds of graphics hardware acceleration does Java2D use?

A: This question has many answers, which depend on things like:
  • What release you are running (we are constantly improving our hardware acceleration renderers, so each release has more to offer)
  • What OS you are running (our story on Windows is different from our story on Linux which is again different from our story on Solaris ... and the Apple folks have their own story for their platform)
  • What kind of hardware you have (your graphics card capabilities can be the single most important factor in enabling Java2D hardware acceleration).
We have tried to write down some of the more interesting and important hardware acceleration features in various articles and blogs. Here are some pointers, but I'm sure you can find more:

Q: How can I get better performance for my Java2D application?

A: The answer depends entirely on what you are trying to do, and what environment you are running on. There are a lot of articles you can read to see if they help you, such as:

Q: Are there any tracing facilities in Java2D to help me track down performance issues in my application?

A: Yes. In J2SE 1.4 and above, Sun's implementation of the Java2D API includes a simple system property that can help you determine the source of your performance bottleneck. Simply run your application with the following system property:

    -Dsun.java2d.trace=[log[,timestamp]],[count],[out:<filename>],[help],[verbose]

For more information on this tracing facility, refer to the following documentation:
http://java.sun.com/j2se/1.5.0/docs/guide/2d/flags.html#trace


Image questions

Q: What image-file formats does Java 2D support?

A: Through its Image I/O package (javax.imageio) Java 2D supports reading and writing GIF, JPEG, PNG, BMP and WBMP images. The Image I/O package includes support for easily plugging in additional image format support: JAI (Java Advanced Imaging) in particular has developed Image I/O plugins that add reading and writing support for formats such as TIFF and JPEG2000 in its "JAI Image I/O Tools" package, which has no dependency on the core JAI libraries.

Q: Which ColorModel should I use if I need optimum performance?

A: The best way to improve your performance with ColorModel optimization is to use an opaque ColorModel. If you are using an IndexColorModel, make sure you have no transparent pixel values and no alpha values in cmap arrays, and set hasAlpha to false. If you are using a ComponentColorModel, set Transparency to OPAQUE and hasAlpha to false. If you are using a DirectColorModel, make sure you have no alpha mask, or set the mask to zero. If you are using a BufferedImage, construct it only with one of the types that do not contain alpha.

Q: What image types should I use to get the best performance?

A: If you are creating an image whose data will be copied to the screen, create the image with the same depth and type of the screen. This way, no translation between the image and the screen is necessary before copying the image. To ensure that the depth and type of your image match the screen, use the createImage(w, h) method of your on-screen component which returns an image that has the same depth as the screen. Alternatively, if you are using a BufferedImage, create it with one of the pre-defined types or use GraphicsConfiguration.createCompatibleImage(w, h) to match the image with the screen format.

Q: How do I create a resized copy of an image?

A: Here's one way to do it: create a BufferedImage of the desired size and draw the original image into it, scaling on the fly. Note that depending on whether your original image is opaque or non-opaque (that is, if it's translucent or transparent), you may need to create an image with an alpha channel.
BufferedImage createResizedCopy(Image originalImage, 
                                int scaledWidth, int scaledHeight, 
                                boolean preserveAlpha)
{
    int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
    BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
    Graphics2D g = scaledBI.createGraphics();
    if (preserveAlpha) {
       g.setComposite(AlphaComposite.Src);
    }
    g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null); 
    g.dispose();
    return scaledBI;
}
You can control the quality of the scaled copy with the Graphics2D.setRenderingHint() method.
See http://java.sun.com/j2se/1.5.0/docs/api/java/awt/RenderingHints.html#KEY_INTERPOLATION
Add the following before the drawImage() call:
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                   RenderingHints.VALUE_INTERPOLATION_[NEAREST_NEIGHBOR
                                                       BILINEAR
                                                       BICUBIC]); 

Q: There are several types of images in Java2D; how do I know which one is appropriate in a particular situation?

A: This article attempts to address that general question: http://java.sun.com/developer/technicalArticles/Media/imagestrategies/index.html. You might also look at other articles in this blog, particularly the ones on VolatileImage and BufferedImage objects: http://weblogs.java.net/blog/chet

Q: Can I use Java2D to generate dynamic images from my servlet (or other server-side Java application)?

A: Certainly! Java2D isn't just for desktop applications. You can use the Java2D API to dynamically generate images in your server application, and then use the Image I/O API to write the image to a File or to an OutputStream (so that it can be viewed in a browser). For example, you can generate a weather map using real-time weather data, or create a pie chart using data from your accounting database. The possibilites are endless...

Here is a simple example of a servlet that generates a smiley face using Java2D, and then writes a JPEG image that can be viewed in a browser:

import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ImageServlet extends HttpServlet {

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("image/jpeg");

        // Create image
        int width=200, height=200;
        BufferedImage image =
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        // Get drawing context
        Graphics2D g2d = image.createGraphics();

        // Fill background with white
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);

        // Draw a smiley face
        g2d.setColor(Color.YELLOW);
        g2d.fillOval(10, 10, 180, 180);
        g2d.setColor(Color.BLACK);
        g2d.fillOval(40, 40, 40, 40);
        g2d.fillOval(120, 40, 40, 40);
        g2d.fillRect(50, 150, 100, 10);

        // Dispose context
        g2d.dispose();

        // Write image to the output stream
        ServletOutputStream os = response.getOutputStream();        

        ImageIO.write(bi, "jpeg", os);
    }
}

Q: I have an application that reads, writes, and processes images (but does not display them) using Java2D. When I run my application on Solaris or Linux, it complains that X11 is not available. What can I do to make my application work in this environment?

A: When AWT is initialized, it expects to find an Xserver, regardless of whether it is needed for actual display.

Although many image operations using the Image I/O APIs or the JAI optional package might not have any obvious need for display, they often invoke code that needs an AWT resource. For example, calling getGraphics() on a BufferedImage initializes AWT and causes these error messages seen by developers. There is no way to say that a particular API does or does not have this problem; it depends on what particular operations are being invoked, and might also depend on what the application does with the images that is not strictly related to any of the APIs cited above.

There are two possible solutions. As of J2SE 1.4 (and above), the preferred solution is to use the "headless AWT toolkit". This feature allows you to use the Java2D API in a server-side application without the need for a display environment. To specify the headless environment, run your application with the following system property:

    -Djava.awt.headless=true

For releases prior to J2SE 1.4 you can provide a "pseudo X-server" to emulate a display environment. One of these Xserver emulators is Xvfb, available for download at http://www.x.org. Another possibility is to start a VNC server (http://www.realvnc.com) and then run your application in that environment.

Xvfb does still have one possible use in J2SE 1.4: A server application may need to display to an Xserver, but requires no user interaction. The headless toolkit won't support this situation, but xvfb will. This is a completely hypothetical scenario and it is not clear if any real world application exhibits such a behavior.


Font and Text questions

Q: What are the capabilities of the Java 2D text rendering system?

A: These include:

  • Antialiased and sub-pixel (aka LCD) text display
  • Transformed text
  • Text as shapes that can be filled with colours, gradient paints etc.
  • Rendering complex text scripts (eg Indic), Bidi text etc
  • APIs to locate and use platform fonts
  • APIs to directly load and register fonts from files and network resources.

These capabilities are all uniformly supported across platforms both for on-screen display and for printing.

Q: What are the different ways that text can be rendered using Java 2D?

A:

  • java.awt.Graphics2D.drawString() draws a String of text using the current font and other rendering attributes. This is the most
common and recommended API as it will automatically identify and properly handle complex scripts, Bidi etc.

  • java.awt.font.TextLayout object allows you to implement text editing yourself: it includes mixed styles, BIDI text layout, carets, highlighting, hit testing and many other features.

  • java.awt.font.GlyphVector can be used to accomplish
glyph-level manipulation can be accomplished by using GlyphVector and GlyphMetrics classes if you want to implement custom text layout algorithms. Since glyphs are actually shapes, you can do with glyphs anything that you can do with shapes, such as clipping and stroking.

  • Using Swing text components : since Swing is based upon Java 2D JTextField, JTextArea, and JEditorPane, which supports editing and multiple fonts and styles, all utilise the above Java 2D APIs. Many developers will therefore not directly use the 2D text APIs but will use the UI-oriented Swing interfaces.

Q. What font types does Java 2D support?

A. The primary and preferred font format is the TrueType font format. Java 2D also supports Opentype layout tables within TrueType fonts for complex text rendering (Arabic, Indic etc). In addition Java 2D supports Postscript Type1 fonts.

Q: Are any fonts bundled with Java 2D?

A: Yes, the Java SE SDK includes three Lucida TrueType font families: Lucida Sans (Sans Serif), Lucida Typewriter (Terminal/Monospaced) and Lucida Bright (Serif). However, only one font : Lucida Sans Regular is always distributed with the JRE that is installed with Java Plugin for running applets in web browsers to limit download size as TrueType fonts can be quite large. End users can elect to install the other fonts but only Lucida Sans Regular is guaranteed to be always available in any Java application. However since Lucida Sans Regular supports many character sets, such as Basic Latin, Latin 1, Latin Ext A, Greek, Cyrillic, Hebrew, Arabic, currency symbol, super subscripts, number forms, dingbats, it means applications have a guaranteed good base line of support. The font files are located in the $JAVAHOME/jre/lib/fonts directory.

Q: How can I list all available fonts?

A: You may get a list of all available font family names using java.awt.GraphicsEnvironment.getAvailableFontFamilyNames() or may list all the font instances using java.awt.GraphicsEnvironment.getAllFonts() In each case the returned list will include the logical fonts such as "Dialog". java.awt.GraphicsEnvironment. getAvailableFontFamilyNames() is typically the most useful for presenting a menu of fonts to a user as a particular style (eg Bold) can requested for a font by specifying it in the style field of the Font constructor.

For sample code, see the implementation of the Font2DTest demo shipped with the JDK (demo/jfc/Font2DTest/src)

Q: How can I make my custom font available to my Java application?

A: Since Java 2D can locate fonts installed in the O/S one option is to just install the font on your system in the usual platform manner - eg dragging it into the Windows font folder.

If you don't want other applications to use your font then there are two other options

  • If you ship a private JRE with your application you can copy your fonts to $JAVAHOME/jre/lib/fonts since they will be available only to java applications executed by that JRE and will be visible via Java's font enumeration APIs.

  • You may also load custom fonts dynamically - by using java.awt.Font.createFont() to instantiate your fonts from files or network streams.

Fonts instantiated via createFont() offer even more flexibility in limiting the visibility of the font. Font instances using such custom fonts cannot normally be constructed. Instead you must use Font.deriveFont() on the instance returned from the Font.createFont() method. Therefore only code to which you provide a references to the Font can use it. Note that each call to createFont from the same source (file) will create a new distinct Font which will be GC'd only when it is no longer referenced. So if you expect to need to use the created Font in many places simultaneously cache a single copy to derive from rather than re-creating from the source file.

As of Java SE 6, there is a method : GraphicsEnvironment.registerFont() which gives you the ability to make a "created" font available to Font constructors and to be listed via Font enumeration APIs. Font.createFont() and this method combine to provide a way to "install" a Font into the running JRE so it is available just as O/S installed fonts are. However this Font does not persist across JRE invocations.

Q: Why does (eg) a 10 pt font in Java applications appear to have a different size from the same font at 10pt in a native application?

A: Conversion from the size in points into device pixels depends on device resolution as reported by the platform APIs. Java 2D defaults to assuming 72 dpi. Platform defaults vary. Mac OS also uses 72 dpi. Linux desktops based on GTK (Gnome) or Qt (KDE) typically default to 96 dpi and let the end-user customise what they want to use. Windows defaults to 96 dpi (VGA resolution) and also offers 120 dpi (large fonts size) and lets users further specify a custom resolution. So a couple of things can now be seen

  • The DPI reported by platform APIs likely has no correspondence to the true DPI
of the display device
  • Its unlikely that Java 2D's default matches the platform default.

So a typical results is that for Window's default 96 DPI that a 10 pt font in a Java application is 72/96 of the size of the native counterpart.

Note that Swing's Windows and GTK L&Fs do scale fonts based on the system DPI to match the desktop. If you want to do the same in your application you can call java.awt.Toolkit.getScreenResolution() and use this to apply a simple scale to the size you specify for fonts.

Q: How to I find if I use a font to display Cyrillic, or Hindi etc?

A: Use Font.canDisplay() to find out if there is a glyph matching a given Unicode character in a given font.

Q. How can I specify the text antialiasing/font smoothing settings to be used by Swing in applications on Java SE 6?

A. This is generally is a question from users of KDE (on Linux or other Unix) or Windows 2000 who would like to use LCD subpixel text. There's no programmatic way to do it in Java SE 6 (but see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6274842 to see if that has changed since this was written). However if you know what you want you can set a system property :

java -Dawt.useSystemAAFontSettings=lcd
which request to use LCD subpixel text in the most common subpixel configuration There are several useful values for this property as follows :

  • "false" corresponds to disabling font smoothing on the desktop.
  • "on" corresponds to Gnome Best shapes/Best contrast (no equivalent Windows desktop setting)
  • "gasp" corresponds to Windows "Standard" font smoothing [see note (*) below] (no equivalent Gnome desktop setting)
  • "lcd" corresponds to Gnome's "subpixel smoothing" and Windows "ClearType"

The full details are documented in the 2D Flags Guide at http://java.sun.com/javase/6/docs/guide/2d/flags.html#aaFonts

(*) Something that surprises people is that Windows "Standard" font smoothing does not mean always smooth/AA. In fact at typical GUI sizes most text is black and white. It uses information in a TrueType font (the gasp table) which specifies whether to use hinted B&W or greyscale smoothing. So if you expected the JDK to do AA at all sizes with this setting, don't be surprised when it doesn't. If you look closely you'll see windows native apps are behaving similarly. See http://www.microsoft.com/typography/SmoothFonts.mspx.

Q: How do I obtain font metrics?

A: You may obtain a Fontmetrics instance by calling java.awt.Graphics.getFontMetrics(Font) This will decribe the font-wide metrics using the current FontRenderContext of the Graphics instance.

Font wide metrics are not specific to a particular piece of text. It is based on data in the font specified by the font designer to reflect the overall design of the font. Do not expect that, for example, the reported "ascent" of the font will be as high as the highest ascent of any glyph in the font.

If you are looking for accurate bounds for string of text - please look at the next question.

FontMetrics are widely used in helping to layout GUIs. They are also cheap to obtain.

Q. What is the difference between logical, visual and pixel bounds?

Logical bounds are the most commonly used and include the ascent, descent, leading and advance of the text. They are useful to position the text correctly, particularly when appending one string after another or for multiple lines of text but they very likely will not enclose the rendered image. Particular examples of this may be glyphs which extend a pixel to the left of the "origin" at which it is drawn. "W" is a particular example - the leftmost long diagonal may extend to the left of the rendering origin. Glyphs in italic fonts are may commonly extend further to the right than the overall "advance" of the text may indicate. These two issues may lead to text being clipped on the left or the right if placed in a tight area measured using the advance of the string. Adding a couple of pixels of padding at each end is probably the simplest workaround for this.

Similarly large descenders, or diacritics that are used in European languages may extend beyond the reported descent or ascent. If used to create a tight bounding box for a label for example this may clip at the bottom or top. Extra padding is again the simplest solution.

To get the logical bounds of a String one may use any of following methods as appropriate :

* Font.getStringBounds(...)
* TextLayout.getAscent()
* TextLayout.getDescent()
* TextLayout.getLeading()
* TextLayout.getAdvance()
* GlyphVector.getLogicalBounds(...)

Note that ascent, descending, and leading which sum to the height, are also available as font-wide properties. That is commonly used in layout of a UI where you would not want the UI to re-layout when the text changes. These font-wide metrics are also not guaranteed to enclose all the text.

Visual bounds guarantee that the theoretical outlines of the glyphs are enclosed. But it still might not enclose the rendered image. Visual bounds are returned by

  * GlyphVector.getVisualBounds(...)
  * TextLayout.getBounds()

Note that TextLayout.getBounds() in fact returns the union of the visual and logical bounds, including any underline and strikethrough.

Pixel bounds are the most precise for where what matters is the rendered image since pixel bounds are guaranteed to enclose all pixels and can be useful for finding the area required to be cleared to fully erase the text. Note that they depend on many factors (position,FontRenderContext, etc.). Since computing pixel bounds requires that Java 2D retrieve the glyph image and examine it, it is more expensive to compute than logical bounds.

To obtain pixel bounds use:

  * GlyphVector.getPixelBounds(...)
  * TextLayout.getPixelBounds(...) (new in Java SE 6)

Note that TextLayout.getPixelBounds(...) includes the bounds of any underline etc.

Also note that no bounds need completely enclose any other – so use the one that's appropriate to your needs.

Q. What are fractional metrics? What are the consequences of setting the fractional metrics rendering hint on text performance, readability etc? When should I use it?

A. In short fractional metrics doesn't affect performance. Principally it affects the spacing between glyphs (characters) which on low resolution (screen) devices can degrade the overall legibility of the text. Understanding this requires some explanation of the topic.

"Fractional metrics" is sometimes called "Printer matching" on other platforms. It refers to the fact that characters in a font are optimized for particular font size and output device resolution to improve legibility of text. These optimizations involve rounding to the pixel grid and therefore may affect overall widths of individual glyphs as well as distances between them.

The text display system can either choose to round these "advance widths" to an integer so that the text will have consistent spacing, or it can accumulate the positions of the characters using the unrounded, or fractional, values.

If it uses the fractional values then the sequence "wi" may not have consistent spacing between the "w" and the "i" at different points along the line. That variation in spacing creates visual noise which is disruptive to the reading process and so is not preferred as the default on the screen. Note that it is only the inter-glyph spacing which changes, not the glyph images,

If it uses the rounded values then the inter-character spacings are much more uniform, but each character's rounded spacing introduces some error and over a long string that error accumulates with each character displayed in succession. The result may be either a longer or a shorter overall string compared with factional metrics. That error is also dependent on the resolution at which the text is being displayed such that the relative lengths of strings will vary as you change the zoom factor or the resolution of the output device. As a result, such rounded widths are considered "not matched to the final printed output" or "not Printermatched". They also complicate layout if you want to view the same text at different sizes or magnifications - you have to customize the layout for each zoom level or expect the strings to start overrunning or underrunning their static positioning.

Since non-fractional (integer) metrics are required for best screen readibility, that is why integer metrics is the default for Java 2D.

The principal reason for turning on fractional metrics is then to closely approximate the layout at high-device resolution when formatting output to a printer, or for displaying a "print preview".

The simplest way to look at it is that "Fractional metrics" improve WYSIWYG on the printer at the expense of legibility on low DPI screens.


Printing questions

Q:What are the differences between the various printing APIs I see in the JDK?

A. These are the printing APIs in the JDK:

1.1 (aka AWT) printing - java.awt.PrintJob

  • A UI component-oriented printing API
  • Can print only the 1.1 "Graphics" API, not "Graphics2D" extensions

2D printing - java.awt.print.PrinterJob

  • A more flexible API for printing documents
  • Can print all of Java 2D.
  • Callback printing model
  • Integrates with javax.print

Java Printing Service - javax.print packages

  • Adds discovery of printers and capabilities
  • Adds ability to specify job behaviour through attributes
  • Adds ability to install custom print services

In summary 1.1/AWT printing can be used for many basic cases but has largely been superseded by 2D printing (as of J2SE 1.2), and when the javax.print package was added in J2SE 1.4 it was made largely complementary to 2D printing and integrated with it for ease of us. So most applications which are focused on rendering to a printer will find it more natural to centre around PrinterJob.

So the presently recommended way to print for most applications is to use java.awt.print.PrinterJob which has methods which leverage javax.print to enumerate printers, and can accept printing attributes from javax.print which define job printing behaviour such as duplex printing.

Q Why is it so hard to print from Swing?

A. In most cases what this turns out to mean is:

  • "how can I print all a JTable?"
  • "how can I just ask Swing to print the contents of a text component (rather than
have to work hard to do all the pagination of the rendering myself)?"

These have now been addressed: J2SE 5.0 added direct printing support on JTable, see: http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html#print()

In addition, Java SE adds the same capability with javax.swing.JTextComponent.print().

Q: What are the causes of large spool files from java.awt.print.PrinterJob and how can I avoid them?

A: This was much more of a problem in earlier releases but as of J2SE 5.0 most applications should not see a problem. Some of it is platform specific and in general things are better on Windows than Solaris and Linux.

Printing using non opaque colours, gradient paints, and custom paints can cause individual pages (not the whole print job) to be generated as a raster at device resolution. This can be mitigated in a few ways:

  • the application printing code could avoid using these.
  • the application could render a selected area which uses these to an offscreen opaque BufferedImage and then draw that BufferedImage to the printer graphics.
  • the application could lower the device resolution of the print job which may be higher than needed, using the PrinterResolution attribute.

Printing Text on Solaris with non-Postscript standard fonts. In many cases the font requested by an application cannot be expected to be available to a Postscript printer, and the JDK has no way to determine the capabilities of the destination Postscript printer anyway. So often the JDK will generate text as stroked and filled outlines. This is particularly a problem in locales where there is no standard Postscript font support.

For English locales however, Latin text in the standard logical fonts such as Dialog, SansSerif, Serif are mapped directly to Postscript printer fonts and should generate compact Postscript. The same happens in the Japanese locale. Also fonts which are "compatible" such as the TrueType font "Times New Roman" which can be mapped to the Postscript "Times Roman" font will be OK. But keeping to the "logical" fonts is usually the simplest way of getting the most compact output.

Q: When printing using java.awt.PrinterJob, why does it print each page at least twice (and sometimes much more than that)?

A: The root of this is that Java 2D printing needs to be able to print everything that Java 2D can render to the screen, and that includes translucent colours, images etc which cannot always be printed directly in Postscript or GDI except when printing everything as one big image, so the implementation tries to avoid this by calling first to discover the rendering that needs to be done for the page. If its simple opaque rendering then only one more call is needed to render the page. If there are translucent colours then multiple calls are done for "bands" down the page to limit the size of the image being generated and hence constrain peak memory usage.

Q How do I keep the information from getting cut off from the top and left sides of the page when I print using the Java 2D printing API?

Because many printers cannot print on the entire paper surface, the PageFormat specifies the imageable area of the page: this is the portion of the page in which it's safe to render. The specification of the imageable area does not alter the coordinate system; it is provided so that the contents of the page can be rendered so that they don't extend into the area where the printer can't print. If you find that information is clipped from your page, you might need to translate the information to the imageable area of the page, as shown in this sample:

    public int print(Graphics g, PageFormat pf, int pageIndex) 
        throws PrinterException
    {
        Graphics2D g2 = (Graphics2D)g;
        g2.translate(pf.getImageableX(), 
        pf.getImageableY() + 72);
        ...
    }

Q: Why are my 1-pixel wide lines repositioned when I print them? How do I correct this?

A: For horizontal or vertical 1-pixel wide line fills, it is generally better to use fillRect rather than drawLine. The rounding of the drawLine method moves lines by half a device pixel, which make the lines appear more consistent whether or not antialiasing is applied. Therefore, if you have a 1-pixel wide line, the line is moved by half of its width. Because this adjustment occurs at the device-space level, your lines can move by half of a line width on printouts from high-resolution printers.

Q: How do I trouble shoot printing problems in Windows?

First, make sure that you have the latest printer driver. It is advisable to use the printer vendor's version of the driver and you can generally do this by downloading it online at their website. If you have the latest driver and are still having problems, sometimes a simple change in the printer's setting or Window's advanced spool options will fix your problem. You can also go to http://java.sun.com and search the bug archive for similar problem. A workaround may be available there. If not, please file a bug.

Q: How do I scale an image to fit a page?

Here is a sample code that uses the page's imageable width and height to scale an image.

public int print(Graphics graphics, PageFormat pageFormat, int
 pageIndex) throws PrinterException
 {
 if(pageIndex > 0)
    return Printable.NO_SUCH_PAGE;
 Graphics2D g2d = (Graphics2D) graphics;

 //Set us to the upper left corner
 g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
 AffineTransform at = new AffineTransform();
 at.translate(0,0);

 //We need to scale the image properly so that it fits on one page.
 double xScale = pageFormat.getImageableWidth() / m_image.getWidth();
 double yScale = pageFormat.getImageableHeight() / m_image.getHeight();
 // Maintain the aspect ratio by taking the min of those 2 factors and
 using it to scale both dimensions.
 double aspectScale = Math.min(xScale, yScale);

 g2d.drawRenderedImage(m_image, at);
 return Printable.PAGE_EXISTS;
 }

Q: When I print using PDF or HTML using the corresponding DocFlavor, how come it is printed as garbage or plain text?

First, do not assume that any of the defined DocFlavors are supported. Use the PrintService's isDocFlavorSupported to find out which flavors are supported. If you find out that it is not supported but know that your printer can handle this then you may go ahead and print it using the DocFlavor.xxx.AUTOSENSE. This is equivalent to printing a raw data. If on the other hand your printer does not support it you need to write your own PrintService which handles the conversion from PDF/HTML to the printer language e.g. PostScript.

Q: Why is Java unable to print to my CUPS 1.2 printers ?

As noted in the CUPS 1.2 releases notes http://www.cups.org/documentation.php/whatsnew.html CUPS now supports Unix Domain Sockets :

Networking
/Domain Sockets;/
CUPS now supports the much faster UNIX  domain sockets for local printing
As a consequence, by default CUPS on Unix/Linux is now configured to listen for connections on both
  • localhost:631 via a TCP/IP socket connection - this is the same as in
previous releases.
  • a unix domain socket at /var/run/cups/cups.sock

So far so good, except that the unix domain socket is now the default and the CUPS API call cupsServer() now returns the unix domain socket pat /var/run/cups/cups.sock instead of "localhost".

This can be an incompatible change for any pre-1.2 aware client which retrieves that name and expects to be able to use to open a TCP/IP connection.

In particular this breaks Sun's JDK (aka Java/aka Java SE, aka JRE) versions 1.5 and Java SE 6. ie all releases to date (Dec 2006) which connects to the CUPS IPP server to provide access to platform printing support. The symptom may be no printers found, or printers 'not accepting jobs'. This will be patched in the next available minor update releases of each of JDK 1.5 and JDK6. The Sun bug to track this can be viewed at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6500903 and the updated releases in which this is fixed will then be viewable there. The expectation is that this will be fixed in JDK6 update 1 (1.6.0_01) and JDK 1.5 update 12 (1.5.0_12).

Until then there are several workaround options for configuring CUPS 1.2 to work with JDK

1. System-wide :

  • Edit the cups configuration file, usually /etc/cups/cupsd.conf, and locate
and comment out using a # the line that listens on the domain socket. It should look like this when you are done:
# Listen /var/run/cups/cups.sock
  • Ensure the line to listen on TCP/IP port 631 is present and UNcommented :
Listen localhost:631

  • Finally save the file and restart cups
sh /etc/init.d/cups restart

2. Per-user workaround #1 Add a ~/.cups/client.conf file with a "ServerName localhost" line. This will make the CUPS API library prefer localhost as the server to report

3. Per-user workaround #2 Set the CUPS_SERVER environment variable to localhost so that the CUPS API library will use and report that : CUPS_SERVER=localhost; export CUPS_SERVER

This approach could if necessary be set only in the environment in which your Java printing application is run and its effects confined to that.

There is one additional step to the workaround for at least some systems. JDK looks for"libcups.so". This is usually a symlink to the specific version, eg "libcups.so.2" but on systems without a developer package it may not exist. The solution is to locate the cup library on your system and if necessary create a symbolic link. For example

cd /usr/lib
ln -s libcups.so.2 libcups.so


Rendering questions

Q: What is the Rendering Process?

A Rendering is the process of taking a collection of shapes, text, and images and figuring out what colors the pixels should be on a screen or printer. In the Java 2D API, the Graphics2D class is the rendering engine: the Graphics2D object contains state attributes, such as color, and applies these attributes to the primitives when rendering them to various output devices.

The seven different attributes are:

  • paint, which represents the color or pattern rendered to the destination for the text and shape draw and fill methods.
  • stroke, which describes the line style of the outline of shapes which are rendered using the shape draw (but not fill) methods.
  • font, which is the font of the text to be rendered in the text draw methods.
  • rendering hint, which suggests optional algorithms for how a primitive should be rendered, such as whether a faster or more accurate algorithm should be used.
  • transform, which represents the mapping from user space to device space and additional coordinate transforms, such as rotate and scale, to be applied to all primitives.
  • composite, which defines how the paint colors should replace or be mixed or blended with the colors already in the destination for all primitives.
  • clip, which identifies a subset of the pixels in the destination which are allowed to be changed. Pixels which fall outside the clip should never be modified by any primitive.


The following figure illustrates the rendering process:
 

Q: What kinds of shapes can I draw using Java 2D?

A: The Java 2D API provides classes for drawing basic shapes like rectangles and circles but also allows you to draw any arbitrary shape. The following table lists the classes that represent predetermined shapes.
 

Arc2D Represents an arc defined by a bounding rectangle, start angle, angular extent, and a closure type.
CubicCurve2D Represents a cubic parametric curve segment.
Ellipse2D Represents an ellipse defined by a bounding rectangle.
Line2D Represents a line segment in (x, y) coordinate space.
Point2D A point representing a location in (x,y) coordinate space. Points render nothing when drawn or filled, but the Point2D class is used in many of the APIs that manipulate or construct shapes.
QuadCurve2D Represents a quadratic parametric curve segment.
Rectangle2D Represents a rectangle defined by a location (x, y) and dimension (w x h).
RoundRectangle2D Represents a rectangle with rounded corners defined by a location (x, y), a dimension (w x h), and the width and height of the corner arcs.

In addition to these classes that allow you to create common shapes, the Java 2D API provides two other classes that allow you to define odd shapes: GeneralPath and Area. Shapes created with GeneralPath must be created segment by segment, but this means that you can combine straight lines and curved lines into a single shape.  The Area class supports constructive area geometry, which allows you to combine two shapes to create another shape, either by adding or intersecting the shapes, subtracting one shape from another, or by subtracting the intersection of the shapes. To see Java 2D and CAG in action, go to
Constructing Complex Shapes from Geometry Primitives in the Java Tutorial.

You can also create a Shape object from a String by calling getOutline on a TextLayout object.  After creating the Shape, you can perform operations such as filling and transforming on the Shape.

For more information on working with shapes in Java 2D, see the 2D Graphics tutorial.

Q: How do I draw on an image?

A: If your image is an external image in a format such as JPG then first you load it into a BufferedImage using the javax.imageio.ImageIO class Then you can obtain a Graphics2D for rendering to the in-memory copy of the image. Here's a sample that does this:

    BufferedImage bi = null;
    try {
        bi = ImageIO.read(new File("images/bld.jpg"));
    } catch (IOException ioe) {
    }
    Graphics2D g2d = bi.createGraphics();
    g2d.drawLine(10, 10, 20, 20);  // draw a line on the image
    ...
    g2d.dispose();

Q: I have a shape whose outline intersects itself. How do I control the filling of the shape?

A: You can control the filling of an arbitrary shape by setting the winding rule. The winding rule specifies which part of a shape is considered the inside of the shape. If you have an arbitrary shape with an outline that intersects itself, you might get different filling results depending on which winding rule you choose.  Because the GeneralPath class is used to represent such arbitrary shapes, any Shape objects that are instances of GeneralPath can use the setWindingRule(int) method to set the winding rule to either WIND_NON_ZERO or WIND_EVEN_ODD. See the GeneralPath class comments for a description of each winding rule.

Note that any given Shape can only have a single winding rule that applies for its entire outline. If you have a complex combination of shapes that overlap and intersect with each other in many places, it may be easier to use the Area class to combine the shapes with explicit intersection, union, subtraction and exclusive or operations rather than to try to control the action of the multiple overlapping sections with a single winding rule.

Q: What does it mean to flatten a Shape?

A: Flattening is the process of converting the curved lines of a Shape into straight line segments. During rasterization, flattening is often performed on all curved path segments before they are stroked or filled because rendering calculations are much faster for straight lines than for curves. Shape objects provide a getPathIterator(aT, flatness) method which returns a PathIterator object that iterates through the path with only straight line segments that approximate the original path of the Shape. The flatness parameter must be a double value greater than or equal to 0.0 which controls how closely the straight lines approximate the curve. The smaller the number, the closer the straight lines will approximate the curve. The original Shape object is not modified by this method as the flattening is done on the fly as the path is iterated.

Q: How do I draw a quadratic arrow-headed curves?

A: There is no direct API for doing this. However, the following sample code can be used to achieve this effect.

        ...
        GeneralPath path = new GeneralPath();
        float p1x = 10, p1y = 10;   // P1
        float p2x = 100, p2y = 10;  // P2
        float cx = 55, cy = 50;     // Control point of the curve
        float arrSize = 5;          // Size of the arrow segments

        float adjSize = (float)(arrSize/Math.sqrt(2));
        float ex = p2x - cx;
        float ey = p2y - cy;
        float abs_e = (float)Math.sqrt(ex*ex + ey*ey);
        ex /= abs_e;
        ey /= abs_e;


        // Creating quad arrow
        path.moveTo(p1x, p1y);
        path.quadTo(cx, cy, p2x, p2y);
        path.lineTo(p2x + (ey-ex)*adjSize, p2y - (ex + ey)*adjSize);
        path.moveTo(p2x, p2y);
        path.lineTo(p2x - (ey + ex)*adjSize, p2y + (ex - ey)*adjSize);
        ...

Q: Why do I get unexpected results when I use the setTransform method of Graphics2D to perform transformations?

A: Because the setTransform method is not intended for adding new coordinate transformations onto an existing transform. In fact, when you use setTransform, you are overwriting the Graphics2D object's current transform, which might be needed for other purposes, such as applying a scaling transformation to adjust for printer resolution. The setTransform method should ONLY be used to restore the Graphics2D transform to an earlier saved state after performing some temporarily transformed rendering.

To perform transformations, use these steps:

  1. Use getTransform to get the current transform:
    AffineTransform aT = g2.getTransform();
  2. Use the transform, translate, scale, shear, or rotate methods to concatenate a transform:
    g2.transform(...);
  3. Perform the rendering:
    g2.draw(...);
  4. Restore the original transform using setTransform:
    g2.setTransform(aT);


Related Links