 | Level: Intermediate Yannick Saillet (ysaillet@de.ibm.com), Software Engineer, IBM
01 Jun 2004
Most Java developers agree that there's only one domain where Swing/AWT is superior to the Eclipse platform's Standard Widget Toolkit, and that's Java 2D. Until now there has been no easy way to integrate the time-saving features of Java 2D with the superior portability, functionality, and performance of SWT's user interface components, but that's all about to change. In this follow up to his popular tutorial on migrating Swing applications to SWT, Java developer and Eclipse enthusiast Yannick Saillet shows you how easy it can be to paint Java 2D images on your SWT components and Draw2D figures.
SWT (Standard Widget Toolkit) is the widget toolkit that is used on the
Eclipse platform. It can also be used as a serious alternative to Swing/AWT to
build any kind of Java GUI application. With the increasing popularity of the
Eclipse platform over the last two years, SWT has come into the spotlight, and
recently it has begun to replace Swing/AWT in some applications. SWT's
popularity springs from the fact that it is a cross-platform toolkit that makes
use of native widgets and has a similar level of functionality to Swing and
other modern toolkits. With SWT, you never have to choose between portability,
functionality, and performance.
In fact, there is only one domain where Swing/AWT is clearly superior to SWT, and that is Java 2D. Java 2D is a powerful API that was introduced with JDK 1.2 It allows Java developers to employ complex 2D transformations (translations, rotations, scaling, shearing, etc.) when painting on an AWT component. Unfortunately, Java 2D is designed to be used exclusively with the AWT or Swing toolkits, and SWT doesn't yet provide such extended 2D capabilities. As a result, many developers have found they must choose between using Java 2D on the Java platform or doing without its exciting features in favor of using SWT.
In this article, however, you'll learn how to have the best of both worlds. I'll demonstrate a simple technique that will allow you to paint Java 2D images on your SWT components and Draw2D figures. In order to follow the examples,
you should be familiar with Java 2D, AWT, and SWT. Some experience with the Eclipse platform's GEF (Graphical Editing Framework) will also be helpful.
The offscreen image technique
This article presents a simple technique that will allow you to use Java 2D functionality to paint on any SWT widget or Draw2D figure. To compensate for the lack of Java 2D capabilities in SWT, an offscreen AWT image is used to receive the Java 2D paint operations and convert them into toolkit-independent pixel values. The pixel information can then be painted on any SWT component by using another offscreen image, created this time by the SWT toolkit. Figure 1 shows the process by which an AWT offscreen image is translated to an SWT image and then painted on an SWT widget.
Figure 1. Offscreen image technique allowing the use of Java 2D in SWT

The offscreen AWT image shown in Figure 1 is initialized with a transparent background. The Java 2D methods are then invoked on the graphics context of the offscreen image. Like any AWT image, the offscreen image owns a graphic context that is automatically Java 2D-enabled. Once all the Java 2D-specific painting is done, the pixel values of the AWT image are extracted and transferred to an offscreen SWT image. The SWT image is then painted on the SWT component with
the toolkit's GC.drawImage(...) method, as you would for any normal image.
I'll go through each step in the procedure in the sections that follow.
Creating the AWT offscreen image
For the AWT offscreen image, you will use an instance of java.awt.image.BufferedImage. A BufferedImage is an image whose pixel data can be accessed through its API. Having access to the pixel values of the image will allow you to transform it later into an SWT image.
The simplest way to construct an AWT buffered image is to use the constructor public BufferedImage(int width, int height, int imageType). The two first parameters indicate the size the image should have. The third parameter expects a constant specifying the type of image to create. The image type -- the possible values are one of the constants TYPE_XXX declared in the class BufferedImage --- indicates how the pixel data are stored in the image. The most important image types typically used for colored images are as follows:
TYPE_BYTE_INDEXED: With this image type the image will use an indexed color model. An indexed color model means that each color used in the image is indexed in an array of colors. The pixel values only contain the index that the color of the pixel has in the color model. Images of this type are limited to 256 colors -- the size of the color model. Storing the pixel information this way allows a compact representation of the image because each pixel is coded with a single byte.
TYPE_INT_RGB: This type constant indicates that the image uses a direct color model. A direct color model means that the value of each pixel contains the complete information about its color. TYPE_INT_RGB indicates that each pixel is coded on one integer (four bytes). The information coded in each pixel is the red, green, and blue (RGB) components of the color used by the pixel. Each color component is coded on a single byte. Because the whole pixel value is coded on four bytes, one byte is unused. The internal representation of such an image consumes four times more memory as for an image using an indexed color model, but the number of possible colors, that can be used by the image, is increased to about 16 million (256 x 256 x 256).
TYPE_INT_ARGB: As with TYPE_INT_RGB, an image of this type uses a direct color model and codes each pixel on four bytes. The difference is that the fourth byte, which was not used with TYPE_INT_RGB, is here used to represent the transparency of the pixel, sometimes also known as the alpha component of the color. Images of this type can have a transparent background, or they can be translucent.
For the offscreen image technique to work, you want only the pixels that are affected by the Java 2D painting to be transferred onto the surface of the SWT component. In order to ensure this, your starting image must have a transparent background. Therefore, you will use a buffered image of type TYPE_INT_ARGB for the AWT offscreen image.
Painting and extracting the image
The next step in the procedure is to paint the image using Java 2D. You start by getting its Graphics2D context. You can do this by using either the method createGraphics2D() or invoking getGraphics(). Painting on this context automatically modifies the pixel data of the image. After the painting is done, the value of the pixels of the images can be easily and efficiently extracted using the method getRGB(int startX, int startY, int w, int h, int rgbArray, int offset, int scansize). This method allows you to transfer the pixel data of a rectangular area of the image into an array of integers. The getRGB() method parameters are
as follows:
startX, startY are the coordinates in the image of the top left corner of the area to extract.
w, h are the width and height of the area to extract.
rgbArray is the array of integers to receive the pixel values.
offset is the index in the array where the value of the first pixel should be copied.
scansize is the index offset that two pixels will have in the array if they belong to two consecutive lines in the image and have the same index in their respective lines. If this value is the same as the width of the area to extract, the first pixel of a line will be stored in the array at the index following the last pixel of the previous line. If this value is greater than the width of the area to extract, some indices in the array will remain unused between the end of a line and the beginning of the next one in the image.
Storing the pixel data
Figure 2 shows how BufferedImage.getRGB(...) fills your buffer of integers when a rectangular area of the AWT image is extracted. The lower part of the figure represents the buffer of integers. Each box represents one value in the buffer containing the 4-byte ARBG value of one pixel. The numbers in parentheses represent the coordinates of the pixel in the image.
Figure 2. Extracting the pixel values from an AWT image
In this case, you aren't using any offset, which means that the first pixel will be saved in the buffer at the index 0. For scansize you use the width of the area to extract, which means that the first pixel of an extracted line will follow in the buffer the last pixel of the previous line. With these parameters, the buffer of integers must be large enough to contain w*h integers.
Once the color information of each pixel is stored in a simple buffer of integers, this information can be transferred into the SWT offscreen image.
Creating the SWT image
An SWT Image is similar to an AWT BufferedImage in the sense that it makes its pixel data directly accessible for read or write operations. This means that you can set or get the color value of any pixel or group of pixels within the image by reading or modifying directly the data of the image. The SWT API, however, is quite different from its AWT equivalent, and simpler to use.
SWT's Image class provides several constructors that allow you to do the following:
- Load an existing image by passing a file name or an
InputStream as parameter to the constructor. The format of the image must then be one of the supported formats: BMP, GIF, JPG, PNG, Windows ICO, etc.
- Construct an empty image having a specified size. The image can then be painted either by modifying its pixel values or by copying the content of an SWT graphics context (
GC) on it.
- Construct an image initialized with an existing buffer of pixel values.
You will use the third constructor to create an SWT image that is a copy of the painted AWT image.
About the ImageData class
Information about the pixel data of an image is contained in its ImageData. ImageData is a class containing information about the size, color palette, color values, and transparency of an image. You should be particularly interested in the following ImageData fields:
- width and height specify the size of the image.
- depth specifies the color depth of the image. The possible values are 1, 2, 4, 8, 16, 24, or 32, indicating the number of bits used to code the value of each pixel.
- palette contains a
PaletteData object storing the information about the color model of the image. SWT's color model, like AWT's, can be indexed or direct. If the color model is indexed, the PaletteData contains the color index. If it is direct it contains the shift information indicating how the RGB components of a color must be extracted out of the integer value of a pixel.
- data contains a buffer of bytes containing the pixel values. Unlike the AWT buffer, the SWT buffer is not an array of integers containing one color value for each pixel. Instead, it contains byte values. The way the bytes are coded depends on the color depth used. For an 8-bit image, one byte in the array exactly represents the value of one pixel in the image. For a 16-bit image, each pixel value is coded on two bytes in the buffer. These two bytes are stored in the least significant byte order. For a 24- or 32-bit image, each pixel value is coded on three or four bytes in the buffer in the most significant byte order.
- bytesPerLine indicates how many bytes in the buffer are used to code all the pixel values of one single line of pixels in the image.
- transparentPixel defines a pixel value that should be used as transparent color in the image.
You will use a 24-bit image with one transparent color channel. Each pixel in the image will be coded on three bytes in the array in the order of red, green, and blue components.
Transforming the image
Knowing the image data makes transforming the AWT image to SWT easy to do.
You simply transfer the buffer of integers (returned by the AWT image with getRGB(...)) into the buffer of bytes expected by the SWT image. Figure 3 shows how the values should be stored in the buffer of the SWT image.
Figure 3. Write the pixel values into an SWT image
As in Figure 2, the lower part of the above illustration shows an internal representation of the image buffer. The number in parentheses shows the coordinates of the pixel whose color value is represented in the buffer.
Although each pixel is coded on three bytes, in the case of a 24-bit image the size of one line of pixel in the pixel is not always the 3*width. Some indices in the buffer may remain unused between two lines of pixel. To know how many bytes are really used for each line of pixels in the image (so that you can know at which index the following line begins in the buffer), you have to use the value of the field bytesPerLine of the ImageData.
The SWT to Java 2D renderer
Listing 1 shows the source code for a generic renderer implementing the offscreen image technique. The renderer allows the transparent use of Java 2D routines when painting on an SWT component or a Draw2D figure.
Listing 1. An SWT/Draw2D Java 2D renderer
package swtgraphics2d;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.widgets.Display;
/**
* Helper class allowing the use of Java 2D on SWT or Draw2D graphical
* context.
* @author Yannick Saillet
*/
public class Graphics2DRenderer {
private static final PaletteData PALETTE_DATA =
new PaletteData(0xFF0000, 0xFF00, 0xFF);
private BufferedImage awtImage;
private Image swtImage;
private ImageData swtImageData;
private int[] awtPixels;
/** RGB value to use as transparent color */
private static final int TRANSPARENT_COLOR = 0x123456;
/**
* Prepare to render on a SWT graphics context.
*/
public void prepareRendering(GC gc) {
org.eclipse.swt.graphics.Rectangle clip = gc.getClipping();
prepareRendering(clip.x, clip.y, clip.width, clip.height);
}
/**
* Prepare to render on a Draw2D graphics context.
*/
public void prepareRendering(org.eclipse.draw2d.Graphics graphics) {
org.eclipse.draw2d.geometry.Rectangle clip =
graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle());
prepareRendering(clip.x, clip.y, clip.width, clip.height);
}
/**
* Prepare the AWT offscreen image for the rendering of the rectangular
* region given as parameter.
*/
private void prepareRendering(int clipX, int clipY, int clipW, int clipH) {
// check that the offscreen images are initialized and large enough
checkOffScreenImages(clipW, clipH);
// fill the region in the AWT image with the transparent color
java.awt.Graphics awtGraphics = awtImage.getGraphics();
awtGraphics.setColor(new java.awt.Color(TRANSPARENT_COLOR));
awtGraphics.fillRect(clipX, clipY, clipW, clipH);
}
/**
* Returns the Graphics2D context to use.
*/
public Graphics2D getGraphics2D() {
if (awtImage == null) return null;
return (Graphics2D) awtImage.getGraphics();
}
/**
* Complete the rendering by flushing the 2D renderer on a SWT graphical
* context.
*/
public void render(GC gc) {
if (awtImage == null) return;
org.eclipse.swt.graphics.Rectangle clip = gc.getClipping();
transferPixels(clip.x, clip.y, clip.width, clip.height);
gc.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height,
clip.x, clip.y, clip.width, clip.height);
}
/**
* Complete the rendering by flushing the 2D renderer on a Draw2D
* graphical context.
*/
public void render(org.eclipse.draw2d.Graphics graphics) {
if (awtImage == null) return;
org.eclipse.draw2d.geometry.Rectangle clip =
graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle());
transferPixels(clip.x, clip.y, clip.width, clip.height);
graphics.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height,
clip.x, clip.y, clip.width, clip.height);
}
/**
* Transfer a rectangular region from the AWT image to the SWT image.
*/
private void transferPixels(int clipX, int clipY, int clipW, int clipH) {
int step = swtImageData.depth / 8;
byte[] data = swtImageData.data;
awtImage.getRGB(clipX, clipY, clipW, clipH, awtPixels, 0, clipW);
for (int i = 0; i < clipH; i++) {
int idx = (clipY + i) * swtImageData.bytesPerLine + clipX * step;
for (int j = 0; j < clipW; j++) {
int rgb = awtPixels[j + i * clipW];
for (int k = swtImageData.depth - 8; k >= 0; k -= 8) {
data[idx++] = (byte) ((rgb >> k) & 0xFF);
}
}
}
if (swtImage != null) swtImage.dispose();
swtImage = new Image(Display.getDefault(), swtImageData);
}
/**
* Dispose the resources attached to this 2D renderer.
*/
public void dispose() {
if (awtImage != null) awtImage.flush();
if (swtImage != null) swtImage.dispose();
awtImage = null;
swtImageData = null;
awtPixels = null;
}
/**
* Ensure that the offscreen images are initialized and are at least
* as large as the size given as parameter.
*/
private void checkOffScreenImages(int width, int height) {
int currentImageWidth = 0;
int currentImageHeight = 0;
if (swtImage != null) {
currentImageWidth = swtImage.getImageData().width;
currentImageHeight = swtImage.getImageData().height;
}
// if the offscreen images are too small, recreate them
if (width > currentImageWidth || height > currentImageHeight) {
dispose();
width = Math.max(width, currentImageWidth);
height = Math.max(height, currentImageHeight);
awtImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
swtImageData = new ImageData(width, height, 24, PALETTE_DATA);
swtImageData.transparentPixel = TRANSPARENT_COLOR;
awtPixels = new int[width * height];
}
}
}
|
The renderer is contained in a single utility class. The class holds and manages the references to the AWT and the SWT offscreen images required by the offscreen image technique. Further notes:
- The fields
swtImageData and awtPixels are, respectively, the buffer containing the pixel values of the SWT image and the buffer used to contain the pixel values of the AWT image during the pixel transfer.
- The constant
TRANSPARENT_COLOR contains an RGB value that is used as the value for the transparent color in the SWT image. Because you have to define a color to use as the transparent channel to paint the background, you have to reserve one color value for this. In the code I have used a random value of 0x123456. Any pixel using this color value will be treated as being transparent. If this value appears to be a color you need to use for your painting operations, you can use any other value to represent the transparency.
How the renderer works
The SWT/Draw2D Java 2D renderer works as follows:
- Before the renderer can be used, its offscreen images and buffers have to be initialized at a size that is at least as large as the area to paint. This is done when you invoke the method
prepareRendering(...). The method accepts as its parameter either an SWT GC or a Draw2D Graphics object, depending on whether the rendering will be made on an SWT or a Draw2D graphics context.
- Next,
prepareRendering extracts the clip rectangle out of the graphics context -- the clip rectangle is the largest rectangular area where pixels can be modified.
- The private method
prepareRendering(int clipX, int clipY, int clipW, int clipH) is then invoked to prepare the offscreen images for rendering. This method is independent from the type of graphics context used; it can be SWT or Draw2D. The prepareRendering() method works as follows:
- It first checks that the AWT and SWT offscreen images are instantiated and large enough to contain the region to paint; this is done by the method
checkOffScreenImages(clipW, clipH).
- If the offscreen images are already instantiated but are not large enough they are disposed of and recreated at the required size. If the offscreen images are larger than the region to paint, they will be reused and only the part of them corresponding to the region to paint will be modified.
- After this check, the region to paint is filled with the color
TRANSPARENT_COLOR reserved for the transparent channel. The image is then ready to be used for Java 2D operations.
- After the renderer has been prepared to accept Java 2D paint operations in the clip region, the Java 2D
Graphics2D context can be obtained from the AWT BufferedImage. This is the graphics context that will be used for all Java 2D paint routines. Each paint operation modifies the AWT offscreen image.
- When all Java 2D paint operations have been done, the pixels of the painted region have to be transferred from the AWT to the SWT offscreen image, and then painted on the SWT or Draw2D graphics context. This operation is done by the methods
render(GC) or render(Graphics). Both methods invoke internally the private method transferPixels(...), which transfers the pixels from AWT to SWT.
- When the renderer is no longer needed, or resources must be freed, the
dispose() method can be called. It disposes of the offscreen images and buffers used by the renderer. This frees up resources, but it will cost time to recreate the buffers when the renderer is again needed. It will be up to you to evaluate when dispose() should be invoked, depending on how often the component has to be repainted and how large the area to repaint should be.
 |
A usage example
Listing 2 shows how the renderer can be used to paint some rotated text on an SWT Canvas.
Listing 2. Usage example with an SWT Canvas
Canvas canvas = new Canvas(shell, SWT.NO_BACKGROUND);
final Graphics2DRenderer renderer = new Graphics2DRenderer();
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
Point controlSize = ((Control) e.getSource()).getSize();
GC gc = e.gc; // gets the SWT graphics context from the event
renderer.prepareRendering(gc); // prepares the Graphics2D renderer
// gets the Graphics2D context and switch on the antialiasing
Graphics2D g2d = renderer.getGraphics2D();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// paints the background with a color gradient
g2d.setPaint(new GradientPaint(0.0f, 0.0f, java.awt.Color.yellow,
(float) controlSize.x, (float) controlSize.y, java.awt.Color.white));
g2d.fillRect(0, 0, controlSize.x, controlSize.y);
// draws rotated text
g2d.setFont(new java.awt.Font("SansSerif", java.awt.Font.BOLD, 16));
g2d.setColor(java.awt.Color.blue);
g2d.translate(controlSize.x / 2, controlSize.y / 2);
int nbOfSlices = 18;
for (int i = 0; i < nbOfSlices; i++) {
g2d.drawString("Angle = " + (i * 360 / nbOfSlices) + "\u00B0", 30, 0);
g2d.rotate(-2 * Math.PI / nbOfSlices);
}
// now that we are done with Java 2D, renders Graphics2D operation
// on the SWT graphics context
renderer.render(gc);
// now we can continue with pure SWT paint operations
gc.drawOval(0, 0, controlSize.x, controlSize.y);
}
});
|
Notes about the code:
- The renderer is created once and is reused every time the canvas needs to be repainted.
- The canvas is instantiated and a
PaintListener is added onto it to execute the paint operations.
- The graphics context
gc is obtained from the PaintEvent.
- The renderer is prepared on the graphics context, meaning that the offscreen images are prepared to receive the paint operations.
- In the next step, the Java 2D graphics context
g2d is obtained.
- From here, a series of Java 2D paint operations are realized to paint the background with a color gradient and draw a rotated text every 20 degrees. This process involves the use of a translation and several rotations of the graphics context.
- Finally the method
render(GC) is invoked to transfer the Java 2D paint operations onto the SWT graphics context. Java 2D and pure SWT paint operations can be used in the same paint routine.
Results of the rendering operation
The results of the rendering operation are shown in Figure 4. In this example the renderer is not discarded, and its offscreen images and internal buffer are recycled for each painting of the Canvas, saving instantiation and garbage collection time. Remember that if the canvas need not be repainted frequently and if the resources taken by the renderer are too important, it is possible to dispose of the renderer after each paint operation.
Figure 4. Usage example: An SWT canvas is painted with the help of Java 2D routines

Listing 3 shows how the same example could be realized on a Draw2D figure. The painting of the Figure is here realized by overriding the method paintClientArea(Graphics) of a Figure. The usage of the Graphics2DRenderer is exactly the same as in the previous example. The only difference is that the methods prepareRendering and render are this time invoked with a Draw2D Graphics as the parameter, instead of an SWT GC.
Listing 3. Usage example with a Draw2D Figure
final Graphics2DRenderer renderer = new Graphics2DRenderer();
IFigure figure = new Figure() {
protected void paintClientArea(org.eclipse.draw2d.Graphics graphics) {
Dimension controlSize = getSize();
renderer.prepareRendering(graphics); // prepares the Graphics2D renderer
// gets the Graphics2D context
Graphics2D g2d = renderer.getGraphics2D();
(...) // does the Java 2D painting
// now that we are done with Java 2D, renders Graphics2D operation
// on the Draw2D graphics context
renderer.render(graphics);
// now we can continue with pure Draw2D paint operations
graphics.drawOval(0, 0, controlSize.width, controlSize.width);
}
};
|
Summary
The simple technique presented in this article makes it not only possible but quite simple to incorporate Java 2D functionality into your SWT and GEF applications. I've shown you step-by-step how to combine the best features of SWT and Java 2D and paint the results onto any SWT component or GEF Draw2D figure. The technique demonstrated here is as simple as the code examples: It can be realized with just a few lines of code, without any dependency on a foreign library, and without starting a single AWT thread.
The implementation code provided with this article can be applied to any SWT or GEF application. The offscreen image technique works on all platforms, including platforms such as Linux/Motif, where SWT and AWT cannot coexist in the same application. Because it is based on nothing more complex than transferring the pixel values from an AWT image to an SWT image, this technique can also be applied to the transformation of other AWT-based imaging APIs in SWT.
Download | Name | Size | Download method |
|---|
| j-2dswtsrc.zip | | HTTP |
Resources
- Download the source code for the renderer presented in this article.
- Read more about the Java 2D API.
- Mitch Goldstein's tutorial, "Introduction to Java 2D" (developerWorks, July 2002) offers a step-by-step guide to the advanced drawing, text layout, and image manipulation that Java 2D brings to GUI programming.
- Learn how to easily migrate a Swing application to SWT/JFace, with Yannick Saillet's tutorial, "Migrate your Swing application to SWT" (developerWorks, January 2004).
- Randy Hudson's "Create an Eclipse-based application using the Graphical Editing Framework" (developerWorks, July 2003) is a good introduction to the GEF.
- Read more about SWT images, with Joe Winchester's "Taking a look at SWT Images."
- Learn about SWT's painting capabilities with Joe Winchester's "Graphics Context -- Quick on the draw."
- Chengdong Li presents another approach to using 2D transformations in SWT images, with the article "A Basic Image Viewer."
- The Web site of Eclipse.org contains a wealth of information and articles about Eclipse, SWT, and GEF/Draw2D.
- You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.
- For a listing of free Java-based programming tutorials, see the developerWorks Java tutorials page.
- Browse for books on these and other technical topics.
- Interested in test driving IBM products without the typical high-cost entry point or short-term evaluation license? The
developerWorks Subscription provides a low-cost, 12-month, single-user license for WebSphere®, DB2®, Lotus®, Rational®, and Tivoli® products -- including the Eclipse-based WebSphere Studio IDE -- to develop, test, evaluate, and demonstrate your applications.
About the author  | |  | Yannick Saillet is a software engineer at the IBM Laboratory of
Boeblingen, Germany. Yannick joined IBM Germany1998.
He first worked for IBM Learning Services as a
software engineer in several distributed learning projects. He joined
the IBM Boeblingen Laboratory in 2000 and has since been
active in the development of business intelligence products, particularly
as a member of the team responsible for IBM's DB2 Intelligent Miner products.
Yannick received his Masters degree from the ESSTIN (Ecole Superieure des
Sciences et Technologies de l'Ingenieur de Nancy) at the University
of Nancy in France. |
Rate this page
|  |