当前页面: 开发资料首页 → Eclipse 英文资料 → Taking a look at SWT Images
摘要: SWT's Image class can be used to display images in a GUI. The most common source of images is to load from a standard file format such as GIF, JPEG, PNG, or BMP. Some controls, including Buttons and TreeItems, are able to display an Image directly through the setImage(Image) method, but any control's paint event allows images to be drawn through the callback's graphic context. SWT's ImageData class represents the raw data making up an SWT Image and determines the color for each pixel coordinate. This article shows the correct uses of ImageData and Image, shows how to load images from files, and how to achieve graphic effects such as transparency, alpha blending, animation, scaling, and custom cursors. By Joe Winchester, IBM
This first section of this article gives an introduction to colors and shows how an image records the color value of each pixel.
Image image = new Image(display,
"C:/eclipse/eclipse/plugins/org.eclipse.platform_2.0.2/eclipse_lg.gif");
Instead of hard-coding the location of the image, it's more common to load the Image from a folder location relative to a given class. This is done by creating an InputStream pointing to the file with the method Class.getResourceAsStream(String name), and using the result as the argument to the constructor Image(Display display, InputStream inputStream).
The Eclipse package explorer below shows the class com.foo.ShellWithButtonShowingEclipseLogo and the eclipse_lg.gif in the same folder. To following code would load the graphic from its location relative to the class.
Image image = new Image(display,
ShellWithButtonShowingEclipseLogo.class.getResourceAsStream(
"eclipse_lg.gif"));
Once the image has been created it can be used as part of a control such as a Button or Label that is able to render the graphic as part of their setImage(Image image) methods.
Button button = new Button(shell,SWT.PUSH);
button.setImage(image);
Images can be drawn onto using a graphics context that is created with the constructor GC(Drawable drawable) with the Image as the argument.
GC gc = new GC(image);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawText("I've been drawn on",0,0,true);
gc.dispose();
Using a GC to draw onto an Image permanently alters the graphic. More information on how to use a GC is covered in the article Graphics Context - Quick on the draw. When an image is loaded, the first step is to create device independent ImageData represented by the class org.eclipse.swt.graphics.ImageData. Following this, the data is prepared for a specific device by creating an actual Image instance.
As well as loading an Image directly from a file, you can separately create the ImageData object and then construct the Image using Image(Device device, ImageData imageData). The data for an existing Image can be retrieved using getImageData(), although this will not be the same object that was used to create the image. This is because when preparing an image to be drawn onto a screen, properties such as its color depth might be different from the initial image data.
Instances of Image represent an underlying resource that has been prepared for a specific device and they must be disposed when they are no longer required to free up the allocated resource. There is no finalization of resources in SWT when an object is garbage collected. For more information see SWT: The Standard Widget Toolkit: Managing Operating System Resources.
The next section describes how colors are represented by their RGB values, and how PaletteData maps a map pixel value to a particular color.
Color cyanColor = new Color(display,0,255,255);
// ... Code to use the Color
cyanColor.dispose();
The convenience class org.eclipse.swt.graphics.RGB exists in SWT that combines a color's red, green and blue values into a single object.
RGB cyanRGB = new RGB(0,255,255);
Color cyanColor = new Color(display,cyanRGB);
// ... Code to use the Color
cyanColor.dispose();
The Color instance should be disposed when it is no longer required, whereas the RGB has no need to be disposed. This is similar to the relationship between an Image and its ImageData, where Color and Image are device specific objects using underlying native resources, while RGB and ImageData are the underlying model data.
To avoid having to create and manage instances of the commonly used colors, the Display class allow these to be retrieved using the method Display.getSystemColor(int id).
Color cyanColor = display.getSystemColor(SWT.COLOR_CYAN)
When a Color is obtained by an SWT program using the method Display.getSystemColor(int id) method, it must not be disposed. The rule of thumb that works for any SWT resource is "If you created it, you are responsible for disposing it". Because the statement above retrieved the cyan color instance, and didn't explicitly construct it, it should not be disposed.
How a Color is actually represented on the display depends on factors such as the resolution and depth of the display. For more information on this and the SWT color model see SWT Color Model.
The example below is a 48 by 48 square image created with a depth of 1, and an indexed color palette. The indexed palette assigns 0 to be red and 1 to be green (by virtue of their order in the RGB[] in the constructor). The ImageData's un-initialized pixel values will initially be 0 (red), and two for loops set a 34 by 34 square in the center of the ImageData to be 1 (green).
PaletteData
paletteData = new PaletteData(
new RGB[] {new RGB(255,0,0), new
RGB(0,255,0)});
ImageData imageData = new ImageData(48,48,1,paletteData);
for(int
x=11;x<35;x++){
for(int y=11;y<35;y++){
imageData.setPixel(x,y,1);
}
}
Image image = new Image(display,imageData);
The above example has a depth of 1 so it can store 2 colors, but as the color depth of the ImageData increases then so can the number of colors in the palette. An indexed palette can have a 1, 2, 4, or 8 bit depths, and an 8 bit depth provides 2^8 = 256 possible colors. To have a higher color depth (such as 16, 24, or 32) a direct palette must be used.
The value for each pixel represents a combination of the red, green and blue components into a single 24 bit integer. To construct an indexed palette the constructor used allows the red, green and blue color masks to be specified.
PaletteData palette =
new PaletteData(0xFF , 0xFF00 , 0xFF0000);
ImageData imageData = new ImageData(48,48,24,palette);
Using the same technique as earlier, the code iterates over every pixel coordinate setting it to either 0xFF (for red) or 0xFF00 (for green).
for (int x=0;x<48;x++){
for(int y=0;y<48;y++){
if(y >
11 && y < 35 && x > 11 && x < 35){
imageData.setPixel(x,y,0xFF00); // Set the
center to green
} else {
imageData.setPixel(x,y,0xFF); // and
everything else to red
}
}
};
Image image = new Image(display,imageData);
This creates the result below where the image is red with a green center.
Because you can use color depths of 16, 24 and 32 bits with direct palettes, you can represent more colors than are available with an indexed palette whose maximum depth is 8. A color depth of 24 allows you to represent 16 million colors (2^24). The tradeoff however is size, because an indexed palette with a depth of 8 requires one byte per image coordinate whereas a direct palette with a depth of 24 requires three bytes per image coordinate.
With both direct and indexed palettes you can go from an RGB to a pixel value and vice-versa using the public methods int getPixel(RGB rgb) and RGB getRGB(int pixelValue).
When Images are used directly on controls such as Button or Label the native behavior may be that transparent pixels are ignored and drawn in the pixel color specified by the source. Native image transparency however is supported in SWT for operations involving a GC. To illustrate this the following file Idea.gif has a color depth of 8, and the white pixel (index 255 in the palette) set to be the transparent pixel.
The shell below has a Label on the left with a Canvas next to it. The Idea.gif is used as the label's image, and also in the paint event of the Canvas. Because the Label does not support native transparency the original white color of the transparent pixel is used as the background, however the GC in the paint event respects the transparent pixel and the grey background shows through.
Image ideaImage = new
ImageData(getClass().getResourceAsStream("Idea.gif"));
Label label = new Label(shell,SWT.NONE);
label.setImage(ideaImage);
Canvas canvas = new Canvas(shell,SWT.NO_REDRAW_RESIZE);
canvas.addPaintListener(new PaintListener() {
public void
paintControl(PaintEvent e) {
e.gc.drawImage(ideaImage,0,0);
}
});
For the above example I stacked the deck in my favor, because I didn't actually use the idea graphic that is included with eclipse articles in their banner. The reason is that the original graphic is a JPG file which doesn't support transparency, so I used a graphics tool to convert it to a GIF and set the value of the white pixel in the palette to be the transparency pixel. The original Idea.jpg is shown below, and although it looks the same as the Idea.gif, this is because it is on the white background of the HTML browser.
By using the original JPG file this offers a good example of how we to achieve a transparency effect progrmatically by manpulating its ImageData. The ImageData class has a public field transparentPixel to specify which pixel is transparent that can be set once a persisted image file is loaded into an ImageData instance, irrespective of whether the persisted file format supports transparency.
The code below loads the Idea.jpg file in an ImageData object and sets the transparent pixel for the ImageData to be the pixel value of the color white in the palette. The pixel value in the indexed palette that represents white is retrieved by using getPixel(RGB). The manipulated ImageData is used to create an Image transparentIdeaImage that now has the white pixel value specified to be transparent.
ImageData ideaData = new ImageData(
getClass().getResourceAsStream("Idea.jpg"));
int whitePixel =
ideaData.palette.getPixel(new RGB(255,255,255));
ideaData.transparentPixel = whitePixel;
Image transparentIdeaImage = new Image(display,ideaData);
Next a Shell uses the newly created image in a native Label and also in the paint event of a Canvas. A Windows Label does not support native transparency so it still appears with a white background, however the GC for the Canvas uses the existing background color whenever a white pixel is encountered in the source image, so the image appears as transparent.
Label transparentIdeaLabel = new
Label(shell,SWT.NONE);
transparentIdeaLabel.setImage(transparentIdeaImage);
Canvas canvas = new Canvas(shell,SWT.NONE);
canvas.addPaintListener(new PaintListener() {
public void
paintControl(PaintEvent e) {
e.gc.drawImage(transparentIdeaImage,0,0);
}
});
As can be seen from the second of the two images (drawn on the Canvas with the white pixel set to transparent), there are still some patches of white. Closer analysis reveals that this is not a bug, but that these regions are not pure white (255,255,255), but are slighly off-white (such as 255,254,254). The transparent pixel of an ImageData can only be used for a single value. This now presents the next problem to be solved - locate all of the off-white pixels in the ImageData and convert them to pure-white. To do this we will iterate over each pixel in the image data and modify those that are close to white to be pure white.
The first step is to load the image and then iterate over each pixel individually looking at its color. Because Idea.jpg is using a direct palette, the pixel value is an int that contains the red, green and blue component as masked bit areas. These mask value can be obtained from the palette.
ImageData ideaImageData = new ImageData(
getClass().getResourceAsStream("Idea.jpg"));
int redMask = ideaImageData.palette.redMask;
int blueMask = ideaImageData.palette.blueMask;
int greenMask = ideaImageData.palette.greenMask;
For any pixel value we can bitwise AND it with the mask to see what the color component is. The red component is the low order bits so this will be the actual value (from 0 to 255), however the green and blue values need adjusting as they are the high order bits in the pixel value. To make this adjustment the color component can be bit shifted to the right using the >> operator. If you are writing generic code to do this kind of manipulating, take care that direct palettes for color depths of 24 or 32 store their color components with red being the low order bits, however for color depth of 16 the colors are reversed and red is high order with blue being low order. The reason for this is to be the same as how Windows stores images internally so there is less conversion when creating the image.
Two for loops will iterate over the imageData. The first is traversing the image from top to bottom a line at a time, and creates an int[] to hold each line of data. The method ImageData.getPixels(int x, int y, int getWidth, int[] pixels, int startIndex) is used to extract a line at a time from the imageData's bytes. The API for this method is slightly irregular, because rather than returning the resulting data it instead is declared as void, and the resulting pixel data is injected into the int[] that is passed in as a method argument. The int[] of pixels is then iterated over and each value has its red, green and blue component extracted. The desired effect we want is to determine whether the pixel is off-white and if so to make it pure white - a rule that works well is to assume that anything whose red and green component are higher than 230 and blue component higher than 150 is an off-white.
int[] lineData = new
int[ideaImageData.width]; Having manipulated the raw bytes making up the ImageData we have now
successfully changed the off-white values to pure white.
ImageLoader imageLoader = new ImageLoader(); The finished result is shown below.
It doesn't look much different to the original Idea.jpg because it is drawn
on a white background, but when it is drawn on a Canvas with the white pixel set
to be the transparent pixel the background shows through achieving the desired
effect.
ImageData pureWhiteIdeaImageData =
It might seem odd that in
the above code that after loading the Idea_PureWhite.jpg file
the transparent pixel was set to be white. Why not set the transparent pixel
before we used the ImageLoader to create the persisted Idea_PureWhite.jpg file?
The reason is that the JPEG image file format does not support transparency. A
GIF file supports native transparency, however changing the file type to
SWT.IMAGE_GIF on the ImageLoader would not have worked, because GIF supports a
maximum image depth of 8 and uses an indexed palette, whereas the JPEG has an
image depth of 24 and a direct palette. To convert between the two formats would
require analyzing the colours used by the JPEG to create the best fit 256 color
palette, before iterating over each JPEG pixel value and creating the GIF image
data by finding the closest color. Doing this conversion is outside the scope of
this article, although it can be done by most commercial graphics tools. To
match pixel values as the color depth decreases from 24 to 8 involves algorithms
that find the right color match for a block of pixels rather than a single pixel
value, and is why image quality can sometimes be reduced when switching between
different formats.
We have shown how an ImageData is an array of int values representing each
pixel coordinate, and how each pixel value is mapped to a color through the
palette. This allowed us to iterate over the image data for the Idea.jpg, query
pixel values that were close to white, and convert these to a pure white RGB
value. The end result of this was that we were able to create the
Idea_PureWhite.jpg file that can be used as a transparent JPG by setting the
white pixel to be transparent. Transparency works by having a source pixel value
(the image being drawn), a destination pixel value (the image being drawn onto)
and a rule by which the resulting destination pixel value is determined. For
transparency the rule is that the source pixel value is used unless it's
transparent in which case the destination pixel is used. Another technique is to
use alpha values that specify the weight applied to the source relative to the
destination to create the final pixel value. This allows the blending between
the source image and the existing background it is being drawn onto.
Shell shell = new Shell(display); Canvas canvas = new
Canvas(shell,SWT.NO_REDRAW_RESIZE);
The following code creates
an alphaData byte[], and then has two loops. The outer loop y
is from 0 to the imageData's height, and the inner loop
creates a byte[] for the width of the imageData and initializes it with
a value that increases from 0 for the top row through to 255 for the bottom row.
A System.arrayCopy then
builds up the alphaData byte[] with each row.
ImageData fullImageData = new
ImageData("C:/temp/Idea_PureWhite.jpg"); The resulting image is shown below, and the alphaData byte[] makes
the top of the image transparent and the bottom opaque, with a gradual fading
between the two.
To create an effect based on an existing image and a style flag use the
constructor Image(Display display, Image image, int flag). The flag
argument is a static constant of either SWT.IMAGE_COPY, SWT.IMAGE_DISABLE or
SWT.IMAGE_GRAY. Copy creates a new image based on the original but with a copy
of its imageData, whereas Disable and Gray create a new image applying platform
specific effects. The following code shows the Idea.jpg, together with three
more images that we created using the style bits
IMAGE_DISABLE,
IMAGE_GRAY and
IMAGE_COPY. To show that IMAGE_COPY creates a new image
a GC is used to draw onto it that affects only the copied image, not the
original.
Image ideaImage = new Image(display,
While the web browser you're using to read this article should show the Idea_SWT_Animation.gif
file as a sequence with the moving pen, this is not true of native SWT controls
displaying the graphic. The animation must be done programmatically, and the
class org.eclipse.swt.examples.ImageAnalyzer shows how this can be
achieved. The ImageAnalyzer class can be obtained from the SWT
examples project in the Eclipse CVS repository.
When an animated GIF is loaded by the ImageLoader class, each individual
frame is a separate element in the data field array typed to ImageData[].
In the animation sequence each ImageData records how many milliseconds it should
be displayed for in the int field delayTime. The number of times the sequence
should repeat can be retrieved from the field loader.repeatCount, a value of -1
indicates that the animation should repeat indefinitely and
Idea_SWT_Animation.gif has this value. When switching from one frame to the next
there are three ways that the new frame can replace the previous one, specified
by the int field ImageData.disposalMethod. This can take the following values
defined in the constant class org.eclipse.swt.SWT. To conserve on space, animated GIFs are generally optimized to just store the
delta that needs to be applied to the previous image. In the
Idea_SWT_Animation.gif above the 15 frames are shown below. Each frame stores a
delta against the previous image, and this was automatically generated by the
tool I create the animated GIF with. The disposal method for each frame is
DM_NONE so the each image should be drawn on top of the previous one. Each
individual ImageData element has the x and y for its top left corner, as well as
its width and height. The overall size to use can be obtained from the fields
loader.logicalScreenWidth and loader.logicalScreenHeight.
To illustrate how to display an animated GIF in SWT we'll create an
initial Image from the first frame and a counter to store which frame is being
displayed. The image is drawn
a paint event on a Canvas, and a GC is created
that will be used to draw the subsequent frames onto the image.
ImageLoader loader = new ImageLoader(); The body of the example will create a thread that iterates through each
frame, waiting until the delayTime
has passed. For each frame the
ImageData is retrieved from the loader and a temporary Image created. This is
then drawn onto the image being displayed on the canvas, at the x and y position
specified by the frame's ImageData. Because we created the temporary
frameImage we must
dispose it when it's no longer being used to free up the underlying resource.
Thread thread = new Thread(){ The following code loads the Idea.jpg image ,
and scales this to 1/2 and 2 times its original size
using the ImageData.scaledTo(int width, int height). The image is also
resized using GC.drawImage(...), and the example shows two ways to
achieve this. The first technique
is to specify the new width and height as part the paint event. This is
potentially inefficient because the scaling must be done each time the canvas
repaints itself. A more optimized technique is to create an image at the final
desired size, construct
a GC over the this and then paint onto it so a permanent scaled image exists in
the program.
The end result is shown below, and both techniques
produce almost identical results.
final Image image =
new Image(display, final Image scaled050
= new Image(display, final Image
scaledGC200 = new Image(display,(int)(width*2),(int)(height*2)); canvas.addPaintListener(new PaintListener() {
When to use GC scaling, and when to use ImageData.scaledTo(...), depends
on the particular scenario. The GC scaling is faster because it is native, however
it does assume that you have a GC and an Image to work with. Using just the
ImageData means that you don't need to have prepared an Image (that requires
a native resource and requires disposing), and an ImageData can be loaded directly
from a graphic file (using the constructor ImageData(String fileName)
or ImageData(InputStream stream)). By using raw ImageData you are delaying
the point at which you will need native display resources, however you will
eventually need to create an Image from the scaled ImageData before it can be
rendered onto a device.
The final section of this article covers the class org.eclipse.swt.graphics.Cursor
responsible for managing the operating system resource associated with the mouse
pointer. The reason cursors are covered in an article on images is because you
can create arbitrary cursors from images, and they illustrate how image masks
work.
Cursors can be created in two ways, either from a pre-defined style or using
source and mask images.
Every Control can have a cursor associated with it, and when the mouse
pointer moves over the control it changes to the specified cursor.
Changing a cursor also affects any child controls, so if you update the cursor
on a Shell this affects the mouse pointer for anywhere on the shell, although if
the child control itself has an explicit cursor, or uses its own cursor such as
an I bean for Text or Combo, this takes precedence over the parent's defined
cursor. The following code illustrates this, by changing the shell's cursor to
be hand cursor, and the list's cursor to a cross. When the mouse is over the
shell (or its childButton that has no explicit cursor) it is a hand, and when it
is over the list it is a cross.
List list = new List(shell,SWT.BORDER);
Cursors use underlying native resources and should be disposed when they are
no longer required. In the above code this would be when the shell has been
disposed and there are no remaining controls using either the handCursor or
crossCursor fields.
The source and mask image data pixels are combined to determine whether the
cursor pixel should be white, black or transparent. The ImageData can be loaded from files, and Eclipse itself does this for some
of its drag and drop cursors defined in org.eclipse.ui/icons/full/dnd. You
can also directly create and manipulate the image data within your program. The
code sample below creates an indexed palette with two colors. The source and
mask ImageData are 32 by 32 with a color depth of 1. The int[] for the source
and mask define an up arrow, the 0s for the source and 1s for the mask are shown
in bold to show how the arrow is defined with the arrays. 0 and 1 makes white
which is the center of the arrow, 1 and 1 is black for the edge of the arrow,
and 1 and 0 transparent for the remainder. The tip of the arrow is 16,3 so this
is made the cursor hotspot when it is created.
PaletteData paletteData = new PaletteData(new RGB[] { int[] cursorSource = new int[] { int[] cursorMask = new int[] { sourceData.setPixels(0,0,1024,cursorSource,0);
To keep the code listings narrow the above sample used an byte[] cursorSource = new byte[] { Custom cursors need to be disposed in just the same way as pre-defined system
cursors, so when there is no remaining control using the cursor is must be send
the method dispose() to free up the underlying native resource.
Although the above example is for a monochrome cursor, Eclipse 3.0 supports
color cursors on platforms that allow it (such as Windows). Two SWT code snippets
showing how to do this are here: snippet
1, snippet
2.
This article has showed how to create and manipulate SWT images.
Underlying each Image is its ImageData that records the pixel value for each
coordinate, and the palette that maps this to a specific color. By understanding
ImageData it is possible to achieve effects such as transparency, alpha
blending, animation, as well as customized cursors.
Java and all Java-based trademarks and logos are trademarks
or registered trademarks of Sun Microsystems, Inc. in the United States, other
countries, or both.
Windows is a trademark of Microsoft corporation in the United
States, other countries, or both.
Other company, product, and service names may be trademarks or
service marks of others.
for (int y = 0; y < ideaImageData.height; y++) {
ideaImageData.getPixels(0,y,width,lineData,0);
// Analyze
each pixel value in the line
for (int x=0;
x
//
Extract the red, green and blue component
int
pixelValue = lineData[x];
int r = pixelValue & redShift;
int g = (pixelValue & greenShift) >> 8;
int b = (pixelValue & blueShift) >> 16;
if (r > 230 && g > 230 && b > 150){
ideaImageData.setPixel(x,y,0xFFFFFF);
}
}
};
Saving Images
Now that we have the ImageData where all of the whitish pixels have been
converted to white, and the transparency pixel of the palette has been set to be
the color white, we'll save this image so that next time an SWT program needs
the pure white JPF it can just load the file and use it as is. To save ImageData
into a file use the class org.eclipse.swt.graphics.ImageLoader. The
image loader has a public field data typed to ImageData[]. The reason the data
field is an array of ImageData is to support image file formats with more than
one frame such as animated GIFs or interlaced JPEG files. These are covered more
in the Animation section later.
imageLoader.data = new ImageData[] {ideaImageData};
imageLoader.save("C:/temp/Idea_PureWhite.jpg",SWT.IMAGE_JPEG);
new ImageData("C:/temp/Idea_PureWhite.jpg");
pureWhiteIdeaImageData.transparentPixel =
pureWhiteIdeaImageData.palette.getPixel(new RGB(255,255,255));
final Image transparentIdeaImage = new
Image(display,pureWhiteIdeaImageData);
Canvas canvas = new Canvas(shell,SWT.NONE);
canvas.addPaintListener(new PaintListener() {
public void
paintControl(PaintEvent e) {
e.gc.drawImage(transparentIdeaImage,0,0);
}
});
Blending
Alpha blending is a technique used to merge two pixel values, where the source
and destination pixel each specify an alpha value that weights how much they
will affect the final destination pixel. An alpha value of 255 is full weight,
and 0 is no weight. SWT supports a single alpha
value for the entire ImageData, or else each pixel can have its own
alpha value.
Single alpha value
The int field alphaValue of ImageData is used to specify a
single value that weights how the source pixels are combined with the
destination input to create the destination output. The listing below shows how
the same ImageData for the Idea_PureWhite.jpg is used for three separate images.
The first is the
original, then an alpha
of 128 is used, and finally
an alpha of 64. Note that the same ImageData is continually manipulated by
having its alpha changed before creating each Image, and changing the ImageData
has no affect on Images already constructed using it. This is because the Image
is prepared for display on the device from the ImageData at construction time.
shell.setLayout(new FillLayout());
ImageData imageData = new
ImageData("C:/temp/Idea_PureWhite.jpg");
final Image fullImage =
new Image(display,imageData);
imageData.alpha = 128;
final Image halfImage =
new Image(display,imageData);
imageData.alpha = 64;
final Image quarterImage
= new Image(display,imageData);
canvas.addPaintListener(new PaintListener() {
public void
paintControl(PaintEvent e) {
e.gc.drawImage(fullImage,0,0);
e.gc.drawImage(halfImage,140,0);
e.gc.drawImage(quarterImage,280,0);
}
});
Different alpha value per
pixel
As well as having a single alpha value that applied to all pixels in the source
image, ImageData allows each pixel to have its own individual alpha value. This
is done with the byte[] field alphaData. This allows effects to be
achieved, such as having an image fade from its top to bottom.
int width = fullImageData.width;
int height = fullImageData.height;
byte[] alphaData = new
byte[height * width];
for(int
y=0;y
byte[] alphaRow = new
byte[width];
for(int x=0;x
alphaRow[x] = (byte) ((255 * y) /height);
}
System.arraycopy(alphaRow,0,alphaData,y*width,width);
}
fullImageData.alphaData = alphaData;
Image fullImage = new Image(display,fullImageData);
Image effects
As well as arbitrary image effects that can be achieved by manipulating image
data, SWT provides a number of pre-defined ways of creating new images based on
existing images combined with certain styles. This is used if, for example, you
have an image being used on a toolbar button and you wish to create a version
that can be used to indicate that the button is disabled or that it is inactive.
getClass().getResourceAsStream("/icons/Idea.jpg");
Image
disabledImage = new Image(display,image,SWT.IMAGE_DISABLE);
Image grayImage = new
Image(display,image,SWT.IMAGE_GRAY);
Image copyImage = new
Image(display,ideaImage,SWT.IMAGE_COPY);
GC gc = new
GC(copyImage);
gc.drawText("This is a copy",0,0);
gc.dispose();
Another important topic for images is understanding animation where an image can
contain a number of frames that are animated in sequence. Image animation is
supported by GIF images, where a single image file can contain multiple sets of
ImageData. Web browsers support native animation, and the following image is
made up of 15 frames showing the pen rotating and writing the words SWT beneath
the Idea logo.
<table border="1">
<tr>
<td>DM_FILL_NONE</td>
<td>Leave the previous image in place and just draw the image on top.
Each frame adds to the previous one.</td>
</tr>
<tr>
<td>DM_FILL_BACKGROUND</td>
<td>Fill with the background color before painting each frame. The pixel
value for this is defined in the field loader.backgroundPixel</td>
</tr>
<tr>
<td>DM_FILL_PREVIOUS</td>
<td>Restore the previous picture </td>
</tr>
<tr>
<td>DM_FILL_UNSPECIFIED</td>
<td>No disposal method has been defined</td>
</tr>
</table>
loader.load(getClass().getResourceAsStream("Idea_SWT_Animation.gif"));
Canvas canvas = new Canvas(shell,SWT.NONE);
image = new
Image(display,loader.data[0]);
int imageNumber;
final GC gc = new
GC(image);
canvas.addPaintListener(new PaintListener(){
public void paintControl(PaintEvent event){
event.gc.drawImage(image,0,0);
}
});
public void run(){
long currentTime =
System.currentTimeMillis();
int delayTime =
loader.data[imageNumber].delayTime;
while(currentTime + delayTime * 10 > System.currentTimeMillis()){
// Wait till the delay time has passed
}
display.asyncExec(new Runnable(){
public void run(){
// Increase the variable holding the frame number
imageNumber
= imageNumber == loader.data.length-1 ? 0 : imageNumber+1;
// Draw the new data onto the image
ImageData nextFrameData = loader.data[imageNumber];
Image
frameImage = new Image(display,nextFrameData);
gc.drawImage(frameImage,nextFrameData.x,nextFrameData.y);
frameImage.dispose();
canvas.redraw();
}
});
}
};
shell.open();
thread.start();
In the examples so far we have loaded an image from a file and drawn it on the
GUI at its original size. There are times when this will not always be the case
and you need to stretch or shrink the image, and there are two ways to do achieve
this. The first is to use the GC to stretch and clip it, using GC.drawImage(Image
image, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int
dstWidth, int dstHeight), and the second is to use ImageData.scaledTo(int
width, int height) to create a new ImageData object based on scaling the
receiver.
getClass(),getResourceAsStream("Idea.jpg"));
final int width = image.getBounds().width;
final int height = image.getBounds().height;
image.getImageData().scaledTo((int)(width*0.5),(int)(height*0.5)));
final Image scaled200 = new Image(display,
image.getImageData().scaledTo((int)(width*2),(int)(height*2)));
GC gc = new GC(scaledGC200);
gc.drawImage(image,0,0,width,height,0,0,width*2,height*2);
gc.dispose();
public void paintControl(PaintEvent e) {
e.gc.drawImage(image,0,0,width,height,0,0,(int)(width*0.5),(int)(height*0.5));
e.gc.drawImage(scaled050,100,0);
e.gc.drawImage(scaledGC200,0,75);
e.gc.drawImage(scaled200,225,175);
}
});
Platform cursors
The list of pre-defined styles are SWT constants shown below, together with
sample images although these will vary depending on the operating system and
platform settings.
<table border="1">
<tr>
<td>CURSOR_APPSTARTING</td>
<td></td>
<td>CURSOR_IBEAM</td>
<td></td>
<td>CURSOR_SIZENE</td>
<td></td>
</tr>
<tr>
<td>CURSOR_ARROW</td>
<td></td>
<td>CURSOR_NO</td>
<td></td>
<td>CURSOR_SIZENESW</td>
<td></td>
</tr>
<tr>
<td>CURSOR_CROSS</td>
<td></td>
<td>CURSOR_SIZEALL</td>
<td></td>
<td>CURSOR_SIZENS</td>
<td></td>
</tr>
<tr>
<td>CURSOR_HAND</td>
<td></td>
<td>CURSOR_SIZEE</td>
<td></td>
<td>CURSOR_SIZENW</td>
<td></td>
</tr>
<tr>
<td>CURSOR_HELP</td>
<td></td>
<td>CURSOR_SIZEN</td>
<td></td>
<td>CURSOR_SIZESNWSE</td>
<td></td>
</tr>
<tr>
<td>CURSOR_SIZES</td>
<td></td>
<td>CURSOR_SIZESE</td>
<td></td>
<td>CURSOR_SIZESW</td>
<td></td>
</tr>
<tr>
<td>CURSOR_SIZEWE</td>
<td></td>
<td>CURSOR_UPARROW</td>
<td></td>
<td>CURSOR_WAIT</td>
<td></td>
</tr>
</table>
Button button = new Button(shell,SWT.NONE);
button.setText("Button");
Cursor handCursor = new Cursor(display,SWT.CURSOR_HAND);
shell.setCursor(handCursor);
Cursor crossCursor = new Cursor(display,SWT.CURSOR_CROSS);
list.setCursor(crossCursor);
Custom cursors
As well as using a pre-defined style, a cursor can be created from images
using the constructor Cursor(Device device, ImageData source, ImageData
mask, int hotspotx, int hotspoty). The source imagedata argument is the
graphic for the cursor shape, and the mask is used to specify transparency. The
following example shows how to create a monochrome custom cursor, where the the
source and mask image data have a color depth of 1 and indexed palettes with two
colors. The ImageDatas height and width should be no larger than 32 and it
does not necessarily have to be square, although the mask and source should be
the same size. The hotspot is the point on the cursor that represents the
precise location of the mouse pointer.
<table border="1">
<tr>
<td>Image</td>
<td>Mask</td>
<td>Cursor color</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>Transparent</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>Black</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>Black</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>White</td>
</tr>
</table>
new RGB(0,0,0) , new RGB(255,255,255)
});
ImageData sourceData = new ImageData(32,32,1,paletteData);
ImageData maskData = new ImageData(32,32,1,paletteData);
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, };
maskData.setPixels(0,0,1024,cursorMask,0);
Cursor cursor = new Cursor(display,sourceData,maskData,16,3);
shell.setCursor(cursor);
int[]
to define the source and mask imageData. A byte uses less memory than an
unsigned int, so when creating custom cursors it is more efficient to use a
byte[] instead, such as:
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x01,
(byte)0x00, // etc...
↑返回目录
前一篇: A small cup of SWT
后一篇: PDE Does Plug-ins