Something for the weekend
Java Carousel Part 3: Reflected Images Love Carousels
Although our carosel can take any Java component, what we really need is a dedicated component that looks perfect in the carosel? For example, the image shouldn't be clipped as in our previous examples, it should scale to fit the size the layout manager sets for it. We also need labels and of course that trade mark "wet floor" reflection (original algorithm came from an article by Romain Guy).

As some of you may have picked up by now, I like to build complexity up gradually. So I'm going to start with a new Component called ImageLabel. What's special? It's basically a standard JLabel, but its constructor takes an image, and the component's preferred size is set to the dimensions of that image. Alternatively, if that's not desired (i.e. you want to specify a higher resolution image and ensure it's always scaled down) you can supply a target preferred size. OK, none of that is very different, so the real difference comes in the paint method. It scales the image to fill the set size, meaning as our CaroselLayout sizes the components as they spin around, it will fill the space available to it.

public class ImageLabel extends JLabel{
protected ImageIcon imageIcon = null;

/**
* Creates a new instance of ImageLabel. The prefered width and height will
* be set to the dimensions of the image
*
* @param icon The image to display
*/
public ImageLabel(ImageIcon icon) {
super(icon);
this.imageIcon = icon;
setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
}

/**
* Creates a new instance of ImageLabel, setting the preferred rendering size to
* the supplied dimensions
*
* @param icon The image to place on the label
* @param width The prefered width
* @parem height The prefered height
*/
public ImageLabel(ImageIcon icon, int width, int height){
this(icon);
setPreferredSize(new Dimension(width,height));
}

/**
* Paints the label scaling the image to the appropriate size
*
*/
public void paint(Graphics graphics) {
Image image = this.imageIcon.getImage();
ImageObserver observer = imageIcon.getImageObserver();
((Graphics2D)graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D)graphics).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.drawImage(image, 0,0,getWidth(),getHeight(),0,0,image.getWidth(observer),image.getHeight(observer),observer);
}
}



Pasted Graphic 4

Here we are with a Carosel with 4 of the normal ImageLabels added. It's looking a lot more like our front Row component already, but we need to add reflections and labels. Let's do that by extending our ImageLabel class with ReflectedImageLabel.

public class ReflectedImageLabel extends ImageLabel{
//The text title to display
private String text = "";
private static final Font reference = new Font("Arial",Font.BOLD,14);


/**
* See constructor for image label
*
*/
public ReflectedImageLabel(ImageIcon image){
super(image);
}

public ReflectedImageLabel(ImageIcon image, int width, int height){
super(image,width,height);
}

public ReflectedImageLabel(ImageIcon image, String text, int width, int height){
this(image,width,height);
this.text=text;
}

public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (int) ((double) d.height * 1.5);

return d;
}

public void paint(Graphics graphics) {
Graphics2D g = (Graphics2D) graphics;
Image image = this.imageIcon.getImage();
ImageObserver observer = imageIcon.getImageObserver();

int drawHeight = (int) ((double) getHeight() / 1.5);

g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

g.drawImage(image, 0,0,getWidth(),drawHeight,0,0,image.getWidth(observer),image.getHeight(observer),observer);



BufferedImage reflection;
try{
reflection= new BufferedImage(getWidth(), drawHeight, BufferedImage.TYPE_INT_ARGB);
} catch (Exception e){
return;
}
Graphics2D reflectionGraphics = reflection.createGraphics();

AffineTransform tranform = AffineTransform.getScaleInstance(1.0, -1.0);
tranform.translate(0, -drawHeight);

// draw the flipped image
AffineTransform oldTransform = reflectionGraphics.getTransform();
reflectionGraphics.setTransform(tranform);
//Should be drawn upside down
//could also try
//super.paint(reflectionGraphics)
reflectionGraphics.drawImage(image, 0,0,getWidth(),drawHeight,0,0,image.getWidth(observer),image.getHeight(observer),observer);


reflectionGraphics.setTransform(oldTransform);


GradientPaint painter = new GradientPaint(0.0f, 0.0f,
new Color(0.0f, 0.0f, 0.0f, 0.5f),
0.0f, drawHeight / 2.0f,
new Color(0.0f, 0.0f, 0.0f, 1.0f));

// this use : Ar = Ad*(1-As) and Cr = Cd*(1-As)
reflectionGraphics.setComposite(AlphaComposite.DstOut);
reflectionGraphics.setPaint(painter);
// this will make our image transluent ...
reflectionGraphics.fill(new Rectangle2D.Double(0, 0, getWidth(), drawHeight));

//now draw on the main graphics layer
graphics.drawImage(reflection, 0,drawHeight,observer);

//Debugging
/*
String text = this.getText();
if (text==null)
text="Null";
char chars[] = new char[text.length()];
text.getChars(0,text.length(),chars,0);
graphics.drawChars(chars,0,chars.length,10,getHeight()/2);*/

reflectionGraphics.dispose();

//Draw text if there is any...
if (text.length()>0){
Graphics2D g2d = (Graphics2D) graphics;
Rectangle2D bounds = reference.getStringBounds(text,g2d.getFontRenderContext());
double scaleFactor = (double) getWidth()/ bounds.getWidth();
Font font = new Font("Arial",Font.BOLD,(int) (14.0 * scaleFactor));
g2d.setFont(font);
graphics.setColor(Color.WHITE);
graphics.drawString(text,0,drawHeight+(int)(bounds.getHeight()*scaleFactor));
}
}
}


Once again, nothing very complex here. The constructors basically match those for ImageLabel, with additional provision for text to show in the label (not reflected), and then re-use a piece of code I must have abused a hundred times by now, Romain Guy's reflection code.

public class ReflectedImageLabel extends ImageLabel{
//The text title to display
private String text = "";

private static final Font reference = new Font("Arial",Font.BOLD,14);


/**
* See constructor for image label
*
*/
public ReflectedImageLabel(ImageIcon image){
super(image);
}

public ReflectedImageLabel(ImageIcon image, int width, int height){
super(image,width,height);
}

public ReflectedImageLabel(ImageIcon image, String text, int width, int height){
this(image,width,height);
this.text=text;
}

public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (int) ((double) d.height * 1.5);

return d;
}

public void paint(Graphics graphics) {
Graphics2D g = (Graphics2D) graphics;
Image image = this.imageIcon.getImage();
ImageObserver observer = imageIcon.getImageObserver();

int drawHeight = (int) ((double) getHeight() / 1.5);

g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

g.drawImage(image, 0,0,getWidth(),drawHeight,0,0,image.getWidth(observer),image.getHeight(observer),observer);

BufferedImage reflection;
try{
reflection= new BufferedImage(getWidth(), drawHeight, BufferedImage.TYPE_INT_ARGB);
} catch (Exception e){
return;
}
Graphics2D reflectionGraphics = reflection.createGraphics();

AffineTransform tranform = AffineTransform.getScaleInstance(1.0, -1.0);
tranform.translate(0, -drawHeight);

// draw the flipped image
AffineTransform oldTransform = reflectionGraphics.getTransform();
reflectionGraphics.setTransform(tranform);
//Should be drawn upside down
//could also try
//super.paint(reflectionGraphics)
reflectionGraphics.drawImage(image, 0,0,getWidth(),drawHeight,0,0,image.getWidth(observer),image.getHeight(observer),observer);


reflectionGraphics.setTransform(oldTransform);


GradientPaint painter = new GradientPaint(0.0f, 0.0f,
new Color(0.0f, 0.0f, 0.0f, 0.5f),
0.0f, drawHeight / 2.0f,
new Color(0.0f, 0.0f, 0.0f, 1.0f));

// this use : Ar = Ad*(1-As) and Cr = Cd*(1-As)
reflectionGraphics.setComposite(AlphaComposite.DstOut);
reflectionGraphics.setPaint(painter);
// this will make our image transluent ...
reflectionGraphics.fill(new Rectangle2D.Double(0, 0, getWidth(), drawHeight));

//now draw on the main graphics layer
graphics.drawImage(reflection, 0,drawHeight,observer);
reflectionGraphics.dispose();

//Draw text if there is any...
if (text.length()>0){
Graphics2D g2d = (Graphics2D) graphics;
Rectangle2D bounds = reference.getStringBounds(text,g2d.getFontRenderContext());
double scaleFactor = (double) getWidth()/ bounds.getWidth();
Font font = new Font("Arial",Font.BOLD,(int) (14.0 * scaleFactor));
g2d.setFont(font);
graphics.setColor(Color.WHITE);
graphics.drawString(text,0,drawHeight+(int)(bounds.getHeight()*scaleFactor));
}
}
}

And this brings us all the way back to our first screen shot and example...

Picture 2

Looks good doesn't it? There's lot's of optimization that can be done (caching the reflected image rather than redrawing it each time for example) but I shall leave that to the reader. If you use this code please drop in a link to the blog (much appreciated), and enjoy.

Download the demo or The Whole Project

Note: The iTunes CD Images used in this example are reproduced here by kind permission of Brian Zeitler from PixelNet Design and please note that all copyright remains with Brian and PixelNet Design.
|