Home » Eclipse Projects » Standard Widget Toolkit (SWT) » Displaying animated GIF in table cell, issues with GC
Displaying animated GIF in table cell, issues with GC [message #506779] |
Sat, 09 January 2010 21:40 |
Marcel tör Messages: 73 Registered: July 2009 |
Member |
|
|
I copied and slightly modified the code from
http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/Displayana nimatedGIF.htm
to write my own animation thread. The modifications were necessary
because the animated GIF is to be displayed in a table cell. Hence, I
essentially implemented my own OwnerDrawLabelProvider.
However, I'm having serious issues finding the right GC for the thread
to draw to. Generally one implements draw(Event, Object) and uses the
event's GC to draw to. In my case this fails because I need to start a
dedicated thread from within paint() and by the time its run method
draws the image to the GC the GC is reset/empty.
I tried creating my own GC inside the thread's constructor: this.gc =
new GC(event.gc.getDevice());. This seemed to work at first and while
debugging the thread's run method never failed to draw to this GC.
However, once I step out of the debugger it fails miserably:
Sat Jan 9 22:11:57 192.168.1.6.local.home java[25387] <Error>:
CGContextConcatCTM: invalid context 0x0
2010-01-09 22:12:18.356 java[25387:17b03] It does not make sense to draw
an image when [NSGraphicsContext currentContext] is nil. This is a
programming error. Break on _NSWarnForDrawingImageWithNoCurrentContext
to debug. This will be logged only once. This may break in the future.
Sat Jan 9 22:12:41 192.168.1.6.local.home java[25387] <Error>:
CGContextConcatCTM: invalid context 0x0
I'm pretty much at my wits end and I would very much appreciated to get
some hints as for how to deal with this.
Regards,
Marcel
/**** code *****/
-> called from OwnerDrawLabelProvider#paint(Event, Object) implementation
private void paintAnimated(final Event event, final ImageLoader
imageLoader) {
if (imageLoader == null || ArrayUtils.isEmpty(imageLoader.data)) {
return;
}
final Thread animateThread = new AnimationThread(event, imageLoader);
animateThread.setDaemon(true);
animateThread.start();
}
private class AnimationThread extends Thread {
private Display display;
private GC gc;
private ImageLoader imageLoader;
private Color background;
public AnimationThread(final Event event, final ImageLoader
imageLoader) {
super("Animation");
this.display = event.display;
/*
* If we were to simply reference event.gc it would be
reset/empty by the time it's being used
* in run().
*/
this.gc = new GC(event.gc.getDevice());
this.imageLoader = imageLoader;
this.background = getBackground(event.item, event.index);
}
@Override
public void run() {
/*
* Create an off-screen image to draw on, and fill it with the
shell background.
*/
final Image offScreenImage =
new Image(this.display, this.imageLoader.logicalScreenWidth,
this.imageLoader.logicalScreenHeight);
final GC offScreenImageGC = new GC(offScreenImage);
offScreenImageGC.setBackground(this.background);
offScreenImageGC.fillRectangle(0, 0,
this.imageLoader.logicalScreenWidth,
this.imageLoader.logicalScreenHeight);
Image image = null;
try {
/* Create the first image and draw it on the off-screen image. */
int imageDataIndex = 0;
ImageData imageData = this.imageLoader.data[imageDataIndex];
image = new Image(this.display, imageData);
offScreenImageGC.drawImage(image, 0, 0, imageData.width,
imageData.height, imageData.x,
imageData.y, imageData.width, imageData.height);
/*
* Now loop through the images, creating and drawing each one
on the off-screen image before
* drawing it on the shell.
*/
int repeatCount = this.imageLoader.repeatCount;
while (this.imageLoader.repeatCount == 0 || repeatCount > 0) {
switch (imageData.disposalMethod) {
case SWT.DM_FILL_BACKGROUND:
/* Fill with the background color before drawing. */
offScreenImageGC.setBackground(this.background);
offScreenImageGC.fillRectangle(imageData.x, imageData.y,
imageData.width,
imageData.height);
break;
case SWT.DM_FILL_PREVIOUS:
// Restore the previous image before drawing.
offScreenImageGC.drawImage(image, 0, 0, imageData.width,
imageData.height,
imageData.x, imageData.y, imageData.width,
imageData.height);
break;
}
imageDataIndex = (imageDataIndex + 1) %
this.imageLoader.data.length;
imageData = this.imageLoader.data[imageDataIndex];
image.dispose();
image = new Image(this.display, imageData);
offScreenImageGC.drawImage(image, 0, 0, imageData.width,
imageData.height, imageData.x,
imageData.y, imageData.width, imageData.height);
// Draw the off-screen image.
this.gc.drawImage(offScreenImage, 0, 0);
/*
* Sleeps for the specified delay time (adding commonly-used
slow-down fudge factors).
*/
try {
int ms = imageData.delayTime * 10;
if (ms < 20) {
ms += 30;
}
if (ms < 30) {
ms += 10;
}
Thread.sleep(ms);
} catch (InterruptedException e) {
ColorLabelProvider.this.log.log(new Status(IStatus.ERROR,
ColorLabelProvider.this.log.getBundle().getSymbolicName(),
"Thread interrupted", e));
}
/*
* If the last image was just drawn, decrement the repeat
count and start again.
*/
if (imageDataIndex == this.imageLoader.data.length - 1) {
repeatCount--;
}
}
} catch (SWTException ex) {
ColorLabelProvider.this.log.log(new Status(IStatus.ERROR,
ColorLabelProvider.this.log.getBundle().getSymbolicName(),
"There was an error animating the GIF", ex));
} finally {
if (offScreenImage != null && !offScreenImage.isDisposed()) {
offScreenImage.dispose();
}
if (offScreenImageGC != null && !offScreenImageGC.isDisposed()) {
offScreenImageGC.dispose();
}
if (image != null && !image.isDisposed()) {
image.dispose();
}
}
}
}
--
Marcel Stör, http://www.frightanic.com
Couchsurfing: http://www.couchsurfing.com/people/marcelstoer
Skype: marcelstoer
-> I kill Google Groups posts: http://improve-usenet.org
|
|
|
Re: Displaying animated GIF in table cell, issues with GC [message #507101 is a reply to message #506779] |
Tue, 12 January 2010 07:51 |
Viliam Durina Messages: 13 Registered: July 2009 |
Junior Member |
|
|
I thought that all UI operations should be called solely from the UI trhead: all other threads have to use Display.syncExec (or asyncExec)...
Anyway, I would not use separate Thread for displaying animations: you can do the same task using Display.timerExec and display unlimited number of animations in a single thread. This is more obvious if you have several animations and change the images frequently. This is my AnimatedImage class:
public class AnimatedImage extends Composite {
private final Image[] images;
private int imageIndex = -1;
private final int timeBetweenFrames;
private final Runnable paintRunnable = new Runnable() {
public void run() {
if (isDisposed())
return;
imageIndex++;
if (imageIndex >= images.length)
imageIndex = 0;
setBackgroundImage(images[imageIndex]);
Display.getCurrent().timerExec(timeBetweenFrames, this);
}
};
public AnimatedImage(Composite parent, int style, final Image[] images, final int timeBetweenFrames) {
super(parent, style);
this.images = images;
this.timeBetweenFrames = timeBetweenFrames;
// initial image
Display.getCurrent().syncExec(paintRunnable);
}
}
Viliam
Marcel Stör wrote / napísal(a):
> I copied and slightly modified the code from
> http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/Displayana nimatedGIF.htm
> to write my own animation thread. The modifications were necessary
> because the animated GIF is to be displayed in a table cell. Hence, I
> essentially implemented my own OwnerDrawLabelProvider.
>
> However, I'm having serious issues finding the right GC for the thread
> to draw to. Generally one implements draw(Event, Object) and uses the
> event's GC to draw to. In my case this fails because I need to start a
> dedicated thread from within paint() and by the time its run method
> draws the image to the GC the GC is reset/empty.
>
> I tried creating my own GC inside the thread's constructor: this.gc =
> new GC(event.gc.getDevice());. This seemed to work at first and while
> debugging the thread's run method never failed to draw to this GC.
> However, once I step out of the debugger it fails miserably:
>
> Sat Jan 9 22:11:57 192.168.1.6.local.home java[25387] <Error>:
> CGContextConcatCTM: invalid context 0x0
> 2010-01-09 22:12:18.356 java[25387:17b03] It does not make sense to draw
> an image when [NSGraphicsContext currentContext] is nil. This is a
> programming error. Break on _NSWarnForDrawingImageWithNoCurrentContext
> to debug. This will be logged only once. This may break in the future.
> Sat Jan 9 22:12:41 192.168.1.6.local.home java[25387] <Error>:
> CGContextConcatCTM: invalid context 0x0
>
> I'm pretty much at my wits end and I would very much appreciated to get
> some hints as for how to deal with this.
>
> Regards,
> Marcel
>
>
> /**** code *****/
> -> called from OwnerDrawLabelProvider#paint(Event, Object) implementation
>
> private void paintAnimated(final Event event, final ImageLoader
> imageLoader) {
> if (imageLoader == null || ArrayUtils.isEmpty(imageLoader.data)) {
> return;
> }
> final Thread animateThread = new AnimationThread(event, imageLoader);
> animateThread.setDaemon(true);
> animateThread.start();
> }
>
> private class AnimationThread extends Thread {
>
> private Display display;
>
> private GC gc;
>
> private ImageLoader imageLoader;
>
> private Color background;
>
> public AnimationThread(final Event event, final ImageLoader
> imageLoader) {
> super("Animation");
> this.display = event.display;
> /*
> * If we were to simply reference event.gc it would be reset/empty
> by the time it's being used
> * in run().
> */
> this.gc = new GC(event.gc.getDevice());
> this.imageLoader = imageLoader;
> this.background = getBackground(event.item, event.index);
> }
>
> @Override
> public void run() {
> /*
> * Create an off-screen image to draw on, and fill it with the
> shell background.
> */
> final Image offScreenImage =
> new Image(this.display, this.imageLoader.logicalScreenWidth,
> this.imageLoader.logicalScreenHeight);
> final GC offScreenImageGC = new GC(offScreenImage);
> offScreenImageGC.setBackground(this.background);
> offScreenImageGC.fillRectangle(0, 0,
> this.imageLoader.logicalScreenWidth,
> this.imageLoader.logicalScreenHeight);
> Image image = null;
> try {
> /* Create the first image and draw it on the off-screen image. */
> int imageDataIndex = 0;
> ImageData imageData = this.imageLoader.data[imageDataIndex];
> image = new Image(this.display, imageData);
> offScreenImageGC.drawImage(image, 0, 0, imageData.width,
> imageData.height, imageData.x,
> imageData.y, imageData.width, imageData.height);
>
> /*
> * Now loop through the images, creating and drawing each one on
> the off-screen image before
> * drawing it on the shell.
> */
> int repeatCount = this.imageLoader.repeatCount;
> while (this.imageLoader.repeatCount == 0 || repeatCount > 0) {
> switch (imageData.disposalMethod) {
> case SWT.DM_FILL_BACKGROUND:
> /* Fill with the background color before drawing. */
> offScreenImageGC.setBackground(this.background);
> offScreenImageGC.fillRectangle(imageData.x, imageData.y,
> imageData.width,
> imageData.height);
> break;
> case SWT.DM_FILL_PREVIOUS:
> // Restore the previous image before drawing.
> offScreenImageGC.drawImage(image, 0, 0, imageData.width,
> imageData.height,
> imageData.x, imageData.y, imageData.width,
> imageData.height);
> break;
> }
>
> imageDataIndex = (imageDataIndex + 1) %
> this.imageLoader.data.length;
> imageData = this.imageLoader.data[imageDataIndex];
> image.dispose();
> image = new Image(this.display, imageData);
> offScreenImageGC.drawImage(image, 0, 0, imageData.width,
> imageData.height, imageData.x,
> imageData.y, imageData.width, imageData.height);
>
> // Draw the off-screen image.
> this.gc.drawImage(offScreenImage, 0, 0);
>
> /*
> * Sleeps for the specified delay time (adding commonly-used
> slow-down fudge factors).
> */
> try {
> int ms = imageData.delayTime * 10;
> if (ms < 20) {
> ms += 30;
> }
> if (ms < 30) {
> ms += 10;
> }
> Thread.sleep(ms);
> } catch (InterruptedException e) {
> ColorLabelProvider.this.log.log(new Status(IStatus.ERROR,
> ColorLabelProvider.this.log.getBundle().getSymbolicName(),
> "Thread interrupted", e));
> }
>
> /*
> * If the last image was just drawn, decrement the repeat
> count and start again.
> */
> if (imageDataIndex == this.imageLoader.data.length - 1) {
> repeatCount--;
> }
> }
> } catch (SWTException ex) {
> ColorLabelProvider.this.log.log(new Status(IStatus.ERROR,
> ColorLabelProvider.this.log.getBundle().getSymbolicName(),
> "There was an error animating the GIF", ex));
> } finally {
> if (offScreenImage != null && !offScreenImage.isDisposed()) {
> offScreenImage.dispose();
> }
> if (offScreenImageGC != null && !offScreenImageGC.isDisposed()) {
> offScreenImageGC.dispose();
> }
> if (image != null && !image.isDisposed()) {
> image.dispose();
> }
> }
> }
> }
>
|
|
|
Re: Displaying animated GIF in table cell, issues with GC [message #507540 is a reply to message #507101] |
Wed, 13 January 2010 19:25 |
Marcel tör Messages: 73 Registered: July 2009 |
Member |
|
|
On 12.01.10 08:51, Viliam Durina wrote:
> I thought that all UI operations should be called solely from the UI
> trhead: all other threads have to use Display.syncExec (or asyncExec)...
>
> Anyway, I would not use separate Thread for displaying animations: you
> can do the same task using Display.timerExec and display unlimited
> number of animations in a single thread. This is more obvious if you
> have several animations and change the images frequently.
Thank you very much for your feedback. From where do you invoke your
AnimatedImage? A "regular" LabelProvider for a cell in a table/tree
viewer must return subclasses of Image? Who creates AnimatedImage?
> This is my
> AnimatedImage class:
>
> public class AnimatedImage extends Composite {
>
> private final Image[] images;
> private int imageIndex = -1;
>
> private final int timeBetweenFrames;
>
> private final Runnable paintRunnable = new Runnable() {
> public void run() {
> if (isDisposed())
> return;
> imageIndex++;
> if (imageIndex >= images.length)
> imageIndex = 0;
> setBackgroundImage(images[imageIndex]);
> Display.getCurrent().timerExec(timeBetweenFrames, this);
> }
> };
>
> public AnimatedImage(Composite parent, int style, final Image[] images,
> final int timeBetweenFrames) {
> super(parent, style);
> this.images = images;
> this.timeBetweenFrames = timeBetweenFrames;
> // initial image
> Display.getCurrent().syncExec(paintRunnable);
> }
>
> }
--
Marcel Stör, http://www.frightanic.com
Couchsurfing: http://www.couchsurfing.com/people/marcelstoer
Skype: marcelstoer
-> I kill Google Groups posts: http://improve-usenet.org
|
|
|
Re: Displaying animated GIF in table cell, issues with GC [message #507642 is a reply to message #507540] |
Thu, 14 January 2010 08:54 |
Viliam Durina Messages: 13 Registered: July 2009 |
Junior Member |
|
|
My AnimatedImage is just a widget, which I simply add to a parent Composite. The animation just replaces the background image of a composite, so I don't need a GC. I don't know how to do it with LabelProvider - you have somehow force the TableViewer to pick up a new Image.
Anyway, the snippet at http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/Displayana nimatedGIF.htm does not redraw the image on paint events: if the animation is slow and you switch to other application and back, the image is not redrawn until next image in the animation is to be painted.
Viliam
Marcel Stör wrote / napísal(a):
> On 12.01.10 08:51, Viliam Durina wrote:
>> I thought that all UI operations should be called solely from the UI
>> trhead: all other threads have to use Display.syncExec (or asyncExec)...
>>
>> Anyway, I would not use separate Thread for displaying animations: you
>> can do the same task using Display.timerExec and display unlimited
>> number of animations in a single thread. This is more obvious if you
>> have several animations and change the images frequently.
>
> Thank you very much for your feedback. From where do you invoke your
> AnimatedImage? A "regular" LabelProvider for a cell in a table/tree
> viewer must return subclasses of Image? Who creates AnimatedImage?
>
>> This is my
>> AnimatedImage class:
>>
>> public class AnimatedImage extends Composite {
>>
>> private final Image[] images;
>> private int imageIndex = -1;
>>
>> private final int timeBetweenFrames;
>>
>> private final Runnable paintRunnable = new Runnable() {
>> public void run() {
>> if (isDisposed())
>> return;
>> imageIndex++;
>> if (imageIndex >= images.length)
>> imageIndex = 0;
>> setBackgroundImage(images[imageIndex]);
>> Display.getCurrent().timerExec(timeBetweenFrames, this);
>> }
>> };
>>
>> public AnimatedImage(Composite parent, int style, final Image[] images,
>> final int timeBetweenFrames) {
>> super(parent, style);
>> this.images = images;
>> this.timeBetweenFrames = timeBetweenFrames;
>> // initial image
>> Display.getCurrent().syncExec(paintRunnable);
>> }
>>
>> }
>
>
|
|
|
Re: Displaying animated GIF in table cell, issues with GC [message #508982 is a reply to message #506779] |
Wed, 20 January 2010 23:59 |
Marcel tör Messages: 73 Registered: July 2009 |
Member |
|
|
On 09.01.10 22:40, Marcel Stör wrote:
> I copied and slightly modified the code from
> http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/Displayana nimatedGIF.htm
> to write my own animation thread. The modifications were necessary
> because the animated GIF is to be displayed in a table cell. Hence, I
> essentially implemented my own OwnerDrawLabelProvider.
[...]
Boy, this is tough nut to crack for an SWT/RCP rookie like me...
OwnerDrawLabelProvider really seems the wrong approach as I can't launch
an animation thread in paint() because that thread would loose the GC to
draw to once paint() completes.
So, I tried the suggested update() approach. I managed to animate the
icon in the viewer's column.
I created a regular ColumnLabelProvider and built logic into its
update(ViewerCell) method. If the icon doesn't need to be animated I do
cell.setImage(getImage()), otherwise I determine which frame of the
animated GIF needs to be displayed and turn this frame into a regular
Image. Then cell.setImage(<animated-gif-frame-as-image>) AND
display.timerExec(....) which calls viewer.update() in its run() method.
This will eventually call the provider's update(ViewerCell) again.
The approach has two serious downsides, though:
a) It's a resource hog. Since the animation has 10 frames/second
update() is called every 100ms. This uses about 10% CPU on my MacBook.
Unacceptable.
b) The animation speed is doubled every time the user causes a "regular"
update because I have no chance to determine in update() whether the
call was invoked by the user or by the runnuable in timerExec().
Any other ideas? I'm close to giving up.
Cheers,
Marcel
--
Marcel Stör, http://www.frightanic.com
Couchsurfing: http://www.couchsurfing.com/people/marcelstoer
Skype: marcelstoer
-> I kill Google Groups posts: http://improve-usenet.org
|
|
| |
Goto Forum:
Current Time: Mon Dec 09 05:18:17 GMT 2024
Powered by FUDForum. Page generated in 0.06913 seconds
|