Areas, Text and Colours (Introduction to Computer Graphics Using Java 2D and 3D) Part 2

Loading and Saving of Images with Java 2D

For loading an image in JPEG format, only the method

tmpc009-174_thumb_thumb

is required. The loaded image can then be drawn on the window on the computer screen or on another BufferedImage by the method drawImage that was already explained for the class BufferedImage. The file ImageLoadingExample.java demonstrates how a JPEG image can be loaded and displayed in a window.

In order to save an image in JPEG format which was generated using Java 2D, the following steps are needed. First, a BufferedImage is generated and the desired image is drawn on this BufferedImage. After drawing is completed, the image can be saved by the commands within the try section of the following code excerpt:

tmpc009-175_thumb_thumb


A complete example for saving a self-generated image can be found in the file ImageSavingExample.java.

Textures in Java 2D

Images in the form of JPEG files can be loaded and directly drawn in the corresponding window. But they can also be used as textures to fill areas or shapes. For filling an area by a texture in Java 2D without repetition of the texture, clipping can be applied with respect to the corresponding Shape. First, the JPEG image must be loaded into an instance theImage of the class Image and a Shape s has to be defined which should be filled with the texture. It is recommended to first remember the old clipping area, which can be obtained by the method getClip. Then the method setClip can be used to define the corresponding Shape as the new clipping area. After the texture has been drawn with the drawImage method, the old clipping area should be reactivated with the setClip method.

tmpc009-176_thumb_thumb

If the texture is too small or positioned wrongly, it might not fill the area defined by the Shape s completely.

In order to fill an area repeatedly with a texture, the class TexturePaint can be used. The texture must be available as a BufferedImage buffImage. A rectangle in the constructor of TexturePaint defines the position and the size of a single copy of the texture. So this rectangle defines how the texture is laid like tiles on the plane. The texture will become visible when a given Shape s is filled with it. This corresponds to opening a window with the corresponding shape through which the texture tiles can be seen.

tmpc009-177_thumb_thumb

The file Texture2DExample.java demonstrates the use of the two techniques for displaying textures.

Displaying Text

Printing and displaying text is a field with a long tradition with roots in typography and printing technology. The details of letter and text representation cannot be covered within this topic, so that the following two sections discuss only a very small selection of problems within this complex topic. General information about letter fonts and their representation can, for instance, be found in [3].

For displaying text, a font must be chosen, which will be used for the symbols and characters to be printed. The size of a font is given in the unit pt (points) with 1pt « 0.3515 mm. A font contains more than just the specification of its size and the descriptions of the shapes of its letters or symbols. For each symbol it is necessary to specify the baseline. Not all letters are above the baseline. Letters like “g” or “p” reach below the baseline. Even this information is not sufficient, since symbols in the font can also have different widths. In this case, the font is called proportional font. In a proportional font each symbol or letter has its own width and even the distance between letters is not constant but varies with the combination of the letters.

Fig. 4.6 Italic and boldface printing for letters given in raster graphics

Italic and boldface printing for letters given in raster graphics

Certain pairs of letters are printed closer together than others. Kerning2 refers to this concept. Ligatures even construct a new connected symbol for certain pairs of letters. Double-f as in “coffee” or the combination “fi” as in “first” are subject to ligatures.3 Kerning and ligatures depend on the chosen font.

Another important aspect of fonts are modifications like boldface or italic fonts.

Fonts can be stored in terms of raster or vector graphics. The advantage of raster graphics is that no additional rendering is needed when symbols of the font have to be drawn. The corresponding pixels are directly given by the defined raster image of the symbol. The disadvantage of storing fonts in terms of raster graphics is that individual raster graphics are needed for different sizes of the same font and for the different styles (normal, boldface, italic). As already mentioned in Sect. 2.1, it is not recommended to try to generate fonts in different sizes by applying scaling to the raster graphics images. There are techniques for deriving raster graphics from a normal font for the boldface and the italic style of the font. For the italic style, pixel lines are shifted to the right. The higher the pixel line in the symbol, the more it is shifted to the right. For boldface printing, the whole symbol is copied and shifted one pixel to the right. Figure 4.6 illustrates this technique. The letter on the left is defined in a relatively rough pixel raster. The letter in the middle is the resulting italic version, the letter on the right the boldface printing.

These techniques for italic and boldface fonts and scaling of fonts for different sizes lead to unacceptable results, so that fonts are usually stored in terms of vector graphics. Nevertheless, rendering fonts in an optimal way remains a nontrivial problem [4].

Text in Java 2D

The method g2d.drawString("text",posx,posy); in Java 2D draws a string—here the string “text”—at the position (xpos,ypos). A default font is used to draw the string. Java 2D offers a variety of methods to choose and modify fonts. Since the focus of this topic is not on text representation,

only very few methods will be introduced here. A new font can be chosen by the command

tmpc009-179_thumb[2]

The name of the font is specified by the string “type”. A list of fonts available on the specific computer is obtained with the following lines of code:

tmpc009-180_thumb[2]

All names appearing in this list can be used for type. Arial, Times New Roman, sansserif are typical standard fonts. Possible values STYLE for style are PLAIN (normal), ITALIC (italic), BOLD (boldface) and ITALIC | BOLD (italic and boldface). The integer value size specifies the size of the font in the unit pt, not in pixels. After calling the method g2d.setFont(f), drawString will use the font f.

In addition to the above-mentioned parameters, a font can also be modified in Java 2D by applying transformations to the font.

Font transformedFont = f.deriveFont(affTrans); f is an arbitrary Font and affTrans is an affine transformation of the class AffineTransform. This technique can be applied in connection with the transformation yUp from Sect. 2.8. yUp was used to make the y-axis of a window pointing upwards. This involves a reflection which is applied to all drawn objects, unfortunately also to strings. As a result, the drawString method will produce text which is written upside down. This problem can be solved by transforming the desired font in such a way that its letters would occur upside down. Drawing the upside down symbols again upside down, will let them occur in normal readable fashion. The suitable transformation affTrans to be applied to the font should carry out a reflection with respect to the x-axis and afterwards a translation in the y-direction by the height of the font. Otherwise all letters would occur below instead of above their intended line. This technique was, for instance, applied in the file RotationExample.java to make sure that the shown coordinate system has the correct orientation.

It is also possible to apply transformations to single symbols of a font only. One can define a String s that contains the symbols to be transformed. Then a GlyphVector is derived from the string.

tmpc009-181_thumb[2]

The GlyphVector contains the characters of the String s. f is the Font to be used for the symbols in s. The method gv.getNumGlyphs() returns the number of characters in the GlyphVector and in the String.

tmpc009-182_thumb[2]

yields the coordinates of the i th character. The method

tmpc009-183_thumb[2]

transforms the ith character into a Shape object. The desired AffineTransform at can then be applied to this Shape object to modify and to position it.

tmpc009-184_thumb[2]

Finally, the transformed character transfGlyph can be drawn by the method g2d.fill(transfGlyph). The file TextExample.java demonstrates the use of these methods.

Grey Images and Intensities

So far it was always assumed that a pixel is coloured black or white. The only exception was made in the context of antialiasing techniques for drawing smooth boundaries of curves to reduce the staircasing effect. The human perception of light intensities is mainly a relative one. A 60-watt light bulb is much brighter in comparison to a 20-watt light bulb than a 100-watt light bulb in comparison to a 60-watt light bulb. In both cases the difference is 40 watts. Nevertheless, the 60 watts is three times as bright as 20 watts, whereas 100 watts is not even twice as bright as 60 watts.

For grey-scale and colour images only a finite number of intensity levels can be used. The intensity levels should be chosen according to the human perception. This means the intensity levels should not increase linearly, but exponentially. Starting from the lowest intensity (black for grey-values) I0, the intensities should be chosen according to the rule

tmpc009-185_thumb[2]

up to a maximum intensity In = rnI0 where r > 1 is a constant. The average human vision system is able to distinguish between grey-levels when they differ by at least 1%, i.e., if r > 1.01 holds [6]. Choosing r = 1.01 and assuming a normalised maximum intensity of In = 1 and minimum intensity I0 for a specific output device, from 1.01n I0 < 1 it follows that

tmpc009-186_thumb[2]

grey-levels are sufficient for the representation on the corresponding output device. A finer resolution of the intensity levels would not make a visible difference. Based on these considerations, Table 4.1 contains the maximum number of intensity levels for different output media.

If an output device allows only binary pixels, for instance a black-and-white laser printer, different intensity levels can be represented for the price of a lower resolution. The technique is called halftoning and it combines binary pixels to larger pixels. For instance, if 2 x 2 smaller pixels form a larger pixel, five intensity levels are expressible. Combining 3 x 3 smaller pixels to form a larger pixel allows 10 different intensity levels and the combination of n x n pixels leads to n2 + 1 possible intensity levels. Of course, the resolution is getting worse, the larger the combined pixels are chosen. The coarsened resolution must still be high enough so that the pixel raster is not immediately visible.

Table 4.1 Intensitylevels for different output media (according to [1])

Medium

Io (ca.)

Max. No. of grey-levels

monitor

0.005-0.025

372-533

newspaper

0.1

232

photo

0.01

464

slide

0.001

695

Fig. 4.7 Grey-level representation based on halftoning for a 2 x 2 (top line) and on 3 x 3 pixel matrices (3 bottom lines)

Grey-level representation based on halftoning for a 2 x 2 (top line) and on 3 x 3 pixel matrices (3 bottom lines)

The different intensity levels are achieved by drawing a different number of smaller pixels in the corresponding combined larger pixel. If a larger pixel should be drawn with intensity Jt, k out of the n x n smaller pixels will be drawn. The k pixels should be chosen in such a way that they are neighbouring pixels and they do not form a regular pattern like a line. Otherwise, this might introduce new visual artefacts like stripes in an area of identical intensity values.

Figure 4.7 shows the representation of five grey-levels by a matrix of 2 x 2 pixels and below the representation of ten grey-levels based on a 3 x 3 pixel matrix.

Dither matrices provide a simple way to define which pixels in an n x n matrix should be set to represent different intensity levels. For the intensity level Jt, those pixels should be chosen whose corresponding entries in the dither matrix are greater than k. The five pixel matrices from the first line in Fig. 4.7 are encoded by the dither matrix D2, the ten 3 x 3 pixel matrices by the dither matrix D3 where

tmpc009-188_thumb[2]

Halftoning can also be applied to nonbinary intensity levels in order to refine the intensity levels further. For instance, using 2 x 2 pixel matrices where each pixel can have four different intensity levels yields 13 possible intensity levels:

tmpc009-189_thumb[2]

For instance, the first matrix in the second line represents the fifth intensity level.4 For this intensity level, one of the four pixels should be drawn with the grey-level 2, the other three with the grey-level 1.

Next post:

Previous post: