Tuesday, January 19, 2010

Using animated GIF in CLabel

Have you ever tried to use animated GIF in Eclipse RCP? Unlike using regular image you have to provide your own mechanism that switches between image frames when using animated GIF (more about this is here). Fortunately, you can save in implementing your own thread by using org.eclipse.ui.internal.ImageCycleFeedbackBase and org.eclipse.ui.internal.AnimationEngine (though these classes are still in internal package). Let's say we need to add an animated image to a CLabel. First of all, we extend ImageCycleFeedbackBase:

public class AnimatedLabelFeedback extends ImageCycleFeedbackBase {
 private CLabel label;

 public AnimatedLabelFeedback(Shell parentShell, CLabel item, Image[] images) {
  super(parentShell, images);
  label = item;
 }

 public void initialize(AnimationEngine engine) {
  background = label.getParent().getBackground();
  display = label.getParent().getDisplay();
 }

 public void saveStoppedImage() {
  stoppedImage = label.getImage();
 }

 public void setStoppedImage(Image image) {
  label.setImage(image);
 }

 public void showImage(Image image) {
  if (!label.isDisposed()) {
   label.setImage(image);
  }
 }
}

This is how we attach animated GIF to the newly created CLabel:
label = new CLabel(parent, SWT.SHADOW_NONE);
Image[] images = loadAnimatedGIF(parent.getDisplay(), "icons/obj16/animated.gif");
AnimatedLabelFeedback feedback = new AnimatedLabelFeedback(parent.getShell(), label, images));
animation = new AnimationEngine(feedback,
  -1, // endless
  100 // delay between frames in milliseconds);
animation.schedule();

loadAnimatedGIF() is implemented as follows:
private void loadAnimatedGIF(Display display, String imagePath) {
  URL url = FileLocator.find(Activator.getDefault().getBundle(),
    new Path(imagePath), null);
  ImageLoader imageLoader = new ImageLoader();
  imageLoader.load(url.openStream());
  images = new Image[imageLoader.data.length];
  for (int i = 0; i < imageLoader.data.length; ++i) {
    ImageData nextFrameData = imageLoader.data[i];
    images[i] = new Image(display, nextFrameData);
  }
}
Dispose animation engine when it's not needed anymore (usually in dispose() method):
animation.cancelAnimation();

PS: @since 3.3

3 comments:

marcelstoer said...

How would you go about an animated GIF in a table viewer?
You can't use a "regular" LabelProvider because it can only return Image and you can't use an OwnerDrawLabelProvider because you can't spawn your own animation in the paint() method...because once paint() completes you loose the GC to draw to.

Michael said...

@marcelstoer, in this case you can use AnimatedTableItemFeedback by analogy with AnimatedLabelFeedback, which will hold a reference to TableItem instead of CLabel, and update its image using TableItem.setImage() accordingly... just a guess :)

Bibi said...

Good job !

You do all in on class with code like this:
class AnimatedLabelFeedback extends ImageCycleFeedbackBase {

private final CLabel label;

private final AnimationEngine animation;

public AnimatedLabelFeedback(final CLabel label, final Image[] images) {
super(label.getShell(), images);

this.label = label;
this.label.addListener(SWT.Dispose, new Listener() {
public void handleEvent(final Event e) {
animation.cancelAnimation();
}
});
animation = new AnimationEngine(this, -1, 100);
animation.schedule();
}

@Override
public void initialize(final AnimationEngine engine) {
background = label.getParent().getBackground();
display = label.getParent().getDisplay();
}

@Override
public void saveStoppedImage() {
stoppedImage = label.getImage();
}

@Override
public void setStoppedImage(final Image image) {
label.setImage(image);
}

@Override
public void showImage(final Image image) {
if (!label.isDisposed()) {
label.setImage(image);
}
}
}