Animations java advanced imaging (avec tampon mémoire)

Description

Gestion des animations de sprites et sonorisation au travers d'une classe Java complète. Utilisable en active et passive rendering (grâce au composant étendu JComponent), le tout Double-Buffering pour assurer le polissage du rendu.
Le tampon mémoire est celui disponible dans le meme package sf3.*, nommée SpritesCacheManager elle sert de tampon ou cache selon les options fournies. Toutes les classes présentées sont Serializable ce qui permet de garder une certaine intégrité à tout ce concept. :)

(ajout: sept 2007 - l'animation devient plus fluide avec un MediaTracker centralisé. Toutefois, il serait nécessaire de vérifier le hashCode du mediatracker qui semble ne pas respecter le contrat de la fonction equals(), cette dernière devrait être surchargée pour retourner une égalité lorsque le même MediaTracker est annoncé pour chaque Sprite, or cela ne semble pas être le cas. Une ré-implementation du MediaTracker est à voir donc. )

Source / Exemple :


package sf3;
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.media.jai.GraphicsJAI;
import javax.swing.Timer;
import java.lang.ref.*;
import java.applet.AudioClip;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.util.Formatter;
import java.util.Set;
import java.util.SortedMap;
import java.util.Stack;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;
import sf3.system.CoalescedThreadsMonitor;
import sf3.system.SpritesCacheManager;
import sf3.system.Threaded;

/**

  • Class implemented to handle animations in Java Environnement.
  • @discussion The implemented timer is the most important part of an animation design pattern. It must implement a real time iterator to avoid certain crashes while drawing the animation on screen.
  • /
public class Animation extends Sprite implements Iterator, Runnable, Serializable, Threaded { /** serial version UID to identify serialize operations */ private static final long serialVersionUID = 2323; /** starting frame n°*/ protected int startingFrame; /** prefix to each frame file */ protected String prefix; /** suffix to each frame file */ protected String suffix; /** current frame */ protected int iterator; /** reverse playing
  • @default false*/
protected boolean reverse = false; /** timer */ protected transient Timer timer; /** frames map */ protected transient SortedMap<Integer, Sprite> frames; /** cache */ protected final SpritesCacheManager<Integer, Sprite> spm; /** sound fx */ protected transient Sound sfx; /** sound fx file path */ protected String sfx_path; /** sound directory path */ protected String sfx_dir = ".cache" + File.separator + "soundIO"; /** start time (millisec) */ protected long start = 0; /** last timer tick time (millisec)*/ protected long lastTick = 0; /** current state
  • @see #PLAYING
  • @see #STOPPED
  • @see #PAUSED */
protected int statusID; /** compressed cache (a bit slow on current systems)
  • @default false*/
protected boolean compress = false; /** file swapped cache (enabled sould be recommended for large amount of frames)
  • @default false*/
protected boolean swap = false; /** buffered frames
  • @see BufferedImage
  • @see VolatileImage
  • @see Sprite#cache
  • @default true*/
protected boolean buffered = true; /** transforms enable,
  • @default true*/
protected boolean transform = true; /***/ protected transient ConcurrentHashMap<Integer, Thread> _bufferThreads; /** current running Threads of that Animation */ protected transient Map<Integer, Thread> bufferThreads; /** playing state */ public final static int PLAYING = 0; /** paused state */ public final static int PAUSED = 1; /** stopped state*/ public final static int STOPPED = 2; /** frames amount count */ public int length; /** framerate in millisec used with
  • @see #timer*/
public int frameRate = (int)(1000.0f/24.0f); // milliseconds /** last drawn frame position */ protected int lastFramePosition = 0; /** next,
  • @see #getIteratorValue(int, int)*/
protected final int NEXT = 0; /** previous,
  • @see #getIteratorValue(int, int)*/
protected final int PREVIOUS = 1; /**
  • bufferMap map
*
  • @see #loadBuffer(long)
  • /
protected transient Map<Integer, PhantomReference> bufferMap; /**
  • bufferMap time millisec
  • /
protected long bufferTime = 100; /** automated cache cleanup (not recommended)
  • @default false*/
public boolean auto = false; /***/ private boolean sfxIsRsrc; /***/ private transient CoalescedThreadsMonitor bufferMonitor; /**
  • Initializes an animation with the specified MediaTracker to handle loading.
*
  • @param startingFrame starting frame number included in sprites file names
  • @param endingFrame ending frame number included in sprites file names
  • @param baseLink string path to base
  • @param prefix string prefix to every sprites filenames
  • @param suffix string suffix to every sprites filenames (usually file .extension)
  • @param mime image mime type of Sprites
  • @param size animation size of Sprites
  • @see Sprite#mime
  • @see Sprite#size
  • @discussion (comprehensive description)
  • /
public Animation(String baseLink, boolean rsrcMode, int startingFrame, int endingFrame, String prefix, String suffix, String format, Dimension size) throws URISyntaxException{ super(false, baseLink, rsrcMode, format, size, true); sfxIsRsrc = rsrcMode; this.length = endingFrame - startingFrame + 1; frames = Collections.synchronizedSortedMap(spm = new SpritesCacheManager<Integer, Sprite>(length)); bufferMap = Collections.synchronizedMap(new WeakHashMap<Integer, PhantomReference>(length)); bufferThreads = Collections.synchronizedMap(_bufferThreads = new ConcurrentHashMap<Integer,Thread>(length)); spm.setCompressionEnabled(compress); spm.setSwapDiskEnabled(swap); spm.setAutoCleanupEnabled(auto); this.startingFrame = startingFrame; this.prefix = prefix; this.suffix = suffix; iterator = (reverse)?length-1:0; } /**
  • Initializes the animation to length blank frames that can be drawn on using
*
  • @param length amount of frames to put in this Animation
  • @param size size of the animation in pixels
  • @param mime informal name of image mime type (e.g. image/jpeg)
  • @see #getGraphics()
  • @discussion (comprehensive description)
  • /
public Animation(int length, String format, Dimension size) { super(false, createBufferedImage(size, Sprite.DEFAULT_TYPE), format, size); sfxIsRsrc = innerResource; this.length = length; this.frames = Collections.synchronizedSortedMap((spm = new SpritesCacheManager<Integer, Sprite>(length))); bufferMap = Collections.synchronizedMap(new WeakHashMap<Integer, PhantomReference>(length)); bufferThreads = Collections.synchronizedMap(_bufferThreads = new ConcurrentHashMap<Integer,Thread>(length)); spm.setCompressionEnabled(compress); spm.setSwapDiskEnabled(swap); spm.setAutoCleanupEnabled(auto); iterator = (reverse)?length-1:0; } /** finalize method that clears the cache */ public void finalize() throws Throwable { stopAllActivity(); frames.clear(); super.finalize(); } /** Serialize write method implemented to use with the cache, current state is stored
  • @param out some OOutputStream to write to. */
private void writeObject(ObjectOutputStream out) throws IOException { int pty = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); stopAllActivity(); spm.setSwapDiskEnabled(swap = true); System.out.println("* Animation's serializing..."); boolean interrupt_ = false; try { synchronized(readMonitor.getMonitor(false)) { while(reading) readMonitor.waitOnMonitor(10); writing = true; waitForActivity(loadResource()); out.defaultWriteObject(); sfx = initSound(); if(sfx.load(sfx_path)) { out.writeBoolean(true); BufferedInputStream bis = (innerResource)?new BufferedInputStream(getClass().getResourceAsStream(sfx_path)):new BufferedInputStream(new FileInputStream(sfx_path)); File d = new File(sfx_dir); d.mkdirs(); File sfxFile = File.createTempFile("sfx_", "" + hash, d); sfxFile.deleteOnExit(); out.writeObject(sfxFile); RandomAccessFile raf = new RandomAccessFile(sfxFile, "rws"); byte[] b = new byte[512]; while(bis.read(b) != -1) raf.write(b); out.writeLong(sfxFile.length()); raf.seek(0); b = new byte[512]; while(raf.read(b) != -1){ out.write(b); } raf.close(); bis.close(); } else out.writeBoolean(false); System.out.println("* Animation's been serialized."); Thread.currentThread().setPriority(pty); readMonitor.notifyOnMonitor(); } } catch (InterruptedException ex) { ex.printStackTrace(); interrupt_ = true; } finally { writing = false; synchronized(writeMonitor.getMonitor(false)) { writeMonitor.notifyAllOnMonitor(); } if(interrupt_) Thread.currentThread().interrupt(); } } /** Serialize read method implemented to use with the cache, current state is recovered
  • @param in some OInputStream to read from.*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { int pty = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); System.out.println("* Animation's deserializing..."); in.defaultReadObject(); buffering = false; frames = Collections.synchronizedSortedMap(spm); bufferThreads = Collections.synchronizedMap(_bufferThreads = new ConcurrentHashMap<Integer, Thread>()); bufferMap = Collections.synchronizedMap(new WeakHashMap<Integer, PhantomReference>(length)); if(in.readBoolean()) { synchronized(writeMonitor.getMonitor(false)) { while(writing) { try { writeMonitor.waitOnMonitor(10); } catch(InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } reading = true; File dir = new File(sfx_dir); dir.mkdirs(); File _sfx = (File)in.readObject(); if(_sfx == null) _sfx = File.createTempFile("sfx_", "" + hash, dir); _sfx.deleteOnExit(); long length = in.readLong(); if(!_sfx.exists() || _sfx.length() != length) { FileOutputStream fos = new FileOutputStream(new RandomAccessFile(_sfx, "rws").getFD()); byte[] b = new byte[512]; long readBytes = 0; while(in.read(b) != -1) { readBytes += b.length; fos.write(b); if(readBytes >= length) break; } fos.close(); System.out.println("Animation serialized sfx path: " + sfx_path); } else in.skip(length); sfx_path = _sfx.getCanonicalPath(); setSfx(sfx_path, false, sfx_frame); reading = false; writeMonitor.notifyOnMonitor(); } synchronized(readMonitor.getMonitor(false)) { readMonitor.notifyAllOnMonitor(); } } System.out.println("* Animation's been deserialized."); Thread.currentThread().setPriority(pty); } /***/ public void setRealTimeLength(long millis) { frameRate = (int)((float)millis / (float)length); } /** returns real time length of this Animation
  • @return time length in millis*/
protected long realTimeLength() { return frameRate * length; } /** elapsed period of frames since started
  • @return elapsed amount of frame
  • @see #elapsedTime()*/
private int elapsedFrames() { return (int)Math.floor((float)elapsedTime() / (float)realTimeLength() * (float)length); } /** elapsed period of time since last tick
  • @return elapsed time since last tick
  • @see #lastTick*/
private long elapsedTickTime() { if(status() == PLAYING) return System.currentTimeMillis() - lastTick; else return 0; } /** elapsed frames since lasttick *
  • @return elapsed amout of frame since last tick time
  • @see #elapsedTickTime()*/
private int elapsedTickFrames() { return (int)Math.floor((float)elapsedTickTime() / (float)realTimeLength() * (float)length); } /** adjusts startTime to the animation current timeframe
  • @return new start time in ms if current is to old
  • @see #start */
protected long adjustStartTime() { if(elapsedTime() >= realTimeLength()) return start = System.currentTimeMillis(); else return start; } /** loads one blank frame
  • @param i frame index
  • @return frame Sprite */
private Sprite loadBlank(int i) { System.out.println("sf3.Animation : loading blank sprite at frame " + i); Image img = (!buffered)?(Image)createVolatileImage(size):createBufferedImage(size, _type); Graphics2D g = (Graphics2D)img.getGraphics(); g.setFont(new Font("Arial", Font.ITALIC, 20)); g.setColor(Color.BLACK); g.fillRect(0,0,size.width,size.height); g.setColor(Color.GRAY); g.drawString("("+(i+1)+")", (int)((float)size.width/2.0f), (int)((float)size.height/2.0f)); Sprite sp = (!buffered)?new Sprite((VolatileImage)img, mime, size):new Sprite((BufferedImage) img, mime, size); return sp; } /***/ protected double markFPS() { int newFramePosition = 0; double fps = 0; if ((newFramePosition = getPosition()) != lastFramePosition) { fps = super.markFPS(); lastFramePosition = newFramePosition; } return fps; } /** instances a Sound interface
  • @return one sound interfaced with
  • @see Sound*/
private Sound initSound() { return new Sound() { AudioClip bgSfx = null; Player player = null; boolean bgSfx_playing = false; boolean mp3Enabled = false; String fileRsrc = null; public boolean load(String filename) { if(filename == null || filename == "") return false; if(fileRsrc != null) if(fileRsrc.equals(filename)) return true; URL rsrc = null; try { rsrc = (sfxIsRsrc) ? getClass().getResource(filename) : new File(filename).toURL(); bgSfx = Applet.newAudioClip(rsrc); player = new Player((sfxIsRsrc)?getClass().getResourceAsStream(filename):new BufferedInputStream(new FileInputStream(filename))); } catch (JavaLayerException ex) { ex.printStackTrace(); } catch (MalformedURLException ex) { ex.printStackTrace(); } finally { if(rsrc == null) return false; fileRsrc = filename; return true; } } public void loop() { if(mp3Enabled) { bgSfx_playing = true; Thread t_mp3 = new Thread(new Runnable() { public void run() { while(bgSfx_playing) { try { if(!load(fileRsrc)) { stop(); continue; } if(player instanceof Player) player.play(); } catch (JavaLayerException ex) { ex.printStackTrace(); } } }},"T-loop-mp3"); t_mp3.start(); } else { if(bgSfx instanceof AudioClip) { bgSfx_playing = true; bgSfx.loop(); } } } public void play() { assert fileRsrc != null : "Sound : you must call load(String) once before to play the Sound"; if(mp3Enabled) { if(!load(fileRsrc)) return; Thread t_mp3 = new Thread(new Runnable() { public void run() { try { bgSfx_playing = true; if(player instanceof Player) player.play(); bgSfx_playing = false; } catch (JavaLayerException ex) { ex.printStackTrace(); } }},"T-play-mp3"); t_mp3.start(); } else { if(bgSfx instanceof AudioClip) { bgSfx.play(); bgSfx_playing = true; } } } public void stop() { if(mp3Enabled) { if(player instanceof Player) player.close(); bgSfx_playing = false; } else { if(bgSfx instanceof AudioClip) bgSfx.stop(); bgSfx_playing = false; } } }; } /***/ public int hashCode() { return super.hashCode() + ((frames != null)?frames.hashCode():0); } /***/ public boolean equals(Object o) { if(o instanceof Animation) { Animation a = (Animation)o; Sprite first0, first1, last0, last1; first0 = frames.get(frames.firstKey()); first1 = a.frames.get(a.frames.firstKey()); last0 = frames.get(frames.lastKey()); last1 = a.frames.get(a.frames.lastKey()); return ( ((base != null && a.base != null)?a.base.equals(base):(base == null && a.base == null)) && ((size != null && a.size != null)?a.size.equals(size):(size == null && a.size == null)) && (first0 != null && first1 != null && last0 != null && last1 != null)? (first0.equals(first1) && last0.equals(last1)): (a.frames.isEmpty() && frames.isEmpty()) ); } else return false; } /* status animation @return status @see #state*/ public int status() { if(timer != null) { return statusID = (timer.isRunning())?PLAYING:PAUSED; } else statusID = STOPPED; return statusID; } /** activate buffered images
  • @param b enabled
  • @see Sprite#cache*/
public void setBufferedImageEnabled(boolean b) { buffered = b; } /** activates compressed cache
  • @param b compressed cachee enabled
  • @see SpritesCacheManager#setCompressionEnabled(boolean)*/
public void setCompressedCacheEnabled(boolean b) { compress = b; spm.setCompressionEnabled(b); } /** activates swap disk cache support
  • @param b swapped cache enabled
  • @see SpritesCacheManager#setSwapDiskEnabled(boolean)*/
public void setSwapDiskCacheEnabled(boolean b) { swap = b; spm.setSwapDiskEnabled(b); } /** get state of the current animation
  • @return current state
  • @see #status()*/
public int getState() { return status(); } /** get current allocation size of cache
  • @return current allocation size in percent
  • @see SpritesCacheManager#allocSize()*/
public double allocSize() { return spm.allocSize(); } /** get animation length
  • @return frames amount count of this animation
  • @see #length*/
public int length() { return length; } /** positions iterator to corresponding frame index
  • @param i frame index position */
public long position(int i) { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); assert (i < length && i >= 0) : getClass().getCanonicalName() + " iterator is not in the correct interval!"; iterator = i; } adjustStartTime(); status(); return getTimeFramePosition(); } /***/ /** get position of the iterator
  • @return current iterator value corresponding to frame index*/
public int getPosition() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); return iterator; } } /**
  • Refreshes animation and cache to current params and prints current iterator value.
  • @discussion (comprehensive description)
  • /
public void validate() { super.validate(); spm.setCompressionEnabled(compress); spm.setSwapDiskEnabled(swap); setZoomEnabled(transform, zoom); setFlipEnabled(transform, mirror); } /** Returns current frame sprite
  • @see Sprite
  • @return current frame sprite*/
public Sprite getCurrentSprite() { Sprite sp = null; final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); System.out.println("current sprite called, f n° is " + iterator); if((sp = frames.get(iterator)) == null) sp = loadError(iterator); return refreshSpriteData(sp, (reverse)?length - iterator:iterator); } } /** refreshes one selected Sprite istance to current parameters
  • @param sp Sprite instance to refresh
  • @return the refreshed Sprite instance*/
private Sprite refreshSpriteData(Sprite sp, int id) { System.out.println("Animation's refreshing " + sp.base + "..."); sp.rProgList = rProgList; sp.rWarnList = rWarnList; sp.wProgList = wProgList; sp.wWarnList = wWarnList; validate(); /*if(sp.getThreadGroup() != getThreadGroup()) sp.setThreadGroup(getThreadGroup());*/ sp._type = _type; sp.setMt(mt); sp.setPreferredSize(size); sp.setSize(size); sp.setFlipEnabled(transform, mirror); sp.setZoomEnabled(transform, zoom); sp.setCompositeEnabled(compositeEnabled); sp.setComposite(cps); sp.setPaint(pnt); sp.setColor(clr); sp.setTrackerPty(id); sp.validate(); return sp; } /**
  • Loads frame indiced with the argument.
  • @discussion (comprehensivedescription)
  • @param i index of the frame
  • @return frame sprite at index i
  • /
public Sprite loadFrame(int i) { Sprite sp = null; if(!frames.containsKey(i)) { System.err.print("OK\n\r"); if(base != null) { String path = src + ((innerResource)?"/":File.separator) + prefix + (int)(startingFrame + i) + suffix; System.out.println("loading frame: " + ((innerResource)?getClass().getResource(path):path)); try { sp = new Sprite(path, innerResource, mime, size, buffered); } catch (URISyntaxException ex) { ex.printStackTrace(); sp = loadError(i); } } else sp = loadBlank(i); sp = refreshSpriteData(sp, (reverse)?length - i:i); frames.put(i, sp); return sp; } sp = frames.get(i); sp = refreshSpriteData(sp, (reverse)?length - i:i); return sp; } /** loads a frame sprite with an error notice
  • @return frame sprite with an error notice */
private Sprite loadError(int i) { Image img = (!buffered)?(Image)createVolatileImage(size):createBufferedImage(size, _type); GraphicsJAI g = createGraphicsJAI(img.getGraphics(), this); g.setFont(new Font("Arial", Font.ITALIC, 20)); g.setColor(Color.RED); g.fillRect(0,0,size.width,size.height); g.setColor(Color.GRAY); g.drawString("error_" + i, (int)((float)size.width/2.0f), (int)((float)size.height/2.0f)); Sprite sp = (!buffered)?new Sprite((VolatileImage)img, mime, size):new Sprite((BufferedImage) img, mime, size); return sp; } /**
  • Unloads frame indexed with the argument.
  • @discussion (comprehensive description)
  • @param i index of the frame
  • /
protected void unloadFrame(int i) { if(frames.containsKey(i)) { try { spm.memorySensitiveCallback("memoryClear", spm, new Object[]{frames.get(i)}, new Class[]{Object.class}); } catch (Throwable ex) { ex.printStackTrace(); } } } /** elapsed period of time from start-time since started playing
  • @return elapsed time since started in ms*/
private long elapsedTime() { long t = System.currentTimeMillis() - start; if(t > realTimeLength()) { if(timer != null) timer.stop(); //t = lastTick-start; } return t; } /** current time-frame position
  • @return current time-frame position */
public long getTimeFramePosition() { return (long)(((float)getPosition()/(float)length) * (float)realTimeLength()); } /** positions iterator to the corresponding time-frame
  • @return corresponding new positioned frame index value */
public int position(long timeFrame) { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); if(!reverse) iterator = Math.min(length - 1, Math.max(0, (int)(((float)timeFrame/(float)realTimeLength()) * (float)length))); else iterator = Math.min(length - 1, Math.max(0, length - 1 - (int)(((float)timeFrame/(float)realTimeLength()) * (float)length))); position(iterator); return iterator; } } /** draws Animation
  • @param g2 graphics instance to draw on
  • @see #draw(Graphics2D, AffineTransform)*/
public void draw(Component obs, Graphics2D g2) throws InterruptedException { draw(obs, g2, null); } /***/ private transient Sprite currentSprite = null; /** draws with Transform concatenated
  • @param g2 graphics instance to draw on
  • @param tx transform instance or null*/
public void draw(Component obs, Graphics2D g2, AffineTransform tx) throws InterruptedException { int pty = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(paintMonitor.getMaxPriority()); boolean interrupt_ = false; spm.ensureListCapacity(spm.getInitialListCapacity()); final CoalescedThreadsMonitor monitor0 = validateMonitor; synchronized(monitor0.getMonitor(false)) { if(validating) System.err.println(base + " Animation's waitin' for buffering..."); while(validating) { System.err.print("."); monitor0.waitOnMonitor(10); } try{ System.err.print("OK\n\r"); painting = true; spm.memorySensitiveCallback("draw", currentSprite = getCurrentSprite(), new Object[]{obs, g2, tx}, new Class[]{Component.class, Graphics2D.class, AffineTransform.class}); markFPS(); } catch(InterruptedException e) { e.printStackTrace(); interrupt_ = true; } catch(Throwable t) { t.printStackTrace(); } finally { painting = false; monitor0.notifyOnMonitor(); } } final CoalescedThreadsMonitor monitor1 = paintMonitor; synchronized(monitor1.getMonitor(false)) { monitor1.notifyAllOnMonitor(); } Thread.currentThread().setPriority(pty); spm.ensureListCapacity(spm.getInitialListCapacity()); if(interrupt_) throw new InterruptedException("Animation " + base + " caught an interruption."); } /**
  • Sets animation SFX.
  • @see #sfx
  • @see #initSound()
  • @discussion (comprehensive description)
  • @param frameMark frame index telling when to play the sfx
  • @param sfx_path rsrc path to sound in mpeg-layer-3(.mp3) or PCM (.wav)
  • /
public void setSfx(String sfx_path, boolean rsrcMode, int frameMark) { sfxIsRsrc = rsrcMode; assert (frameMark < length - 1 && frameMark >= 0) : getClass().getCanonicalName() + " frame mark for sfx is not in the correct interval!"; sfx_frame = frameMark; sfx = initSound(); if(sfx_path != null) sfx.load(sfx_path); this.sfx_path = sfx_path; } /**
  • Sets reverse tag to this animation, that means if it has to be played in the reverse direction
  • @discussion (comprehensive description)
  • @param b true to reverse animation, default is false
  • /
public void setReverseEnabled(boolean b) { reverse = b; } /** tells when to play the associated sound FX
  • @see #sfx
  • @see #initSound() */
int sfx_frame = 0; /**
  • Animation run method of Runnable interface. sctually refreshes the animation iterator and loads bufferMap. it plays the SFX too if required * @discussion (comprehensive description)
  • /
public void run() { if(paintMonitor == null) return; boolean interrupt_ = false; final CoalescedThreadsMonitor monitor0 = paintMonitor; synchronized(monitor0.getMonitor(true)) { monitor0.notifyOnMonitor(); boolean sound = (sfx_path != null)?sfx.load(sfx_path):false; if(painting) System.err.println("Animator " + base + " Thread's waitin' for paint..."); while(painting) { System.err.print("."); try { monitor0.waitOnMonitor(10); } catch(InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } validating = true; final CoalescedThreadsMonitor monitor2 = imageSynch; synchronized(monitor2.getMonitor(false)) { monitor2.notifyOnMonitor(); System.err.print("OK\n\r"); System.err.print("f n° : " + iterator + " -> "); if(reverse) { iterator = length - elapsedFrames(); } else { iterator = elapsedFrames(); } iterator = Math.min(iterator, length - 1); iterator = Math.max(iterator, 0); System.err.print(iterator + "\n\r"); } lastTick = System.currentTimeMillis(); if(sfx_frame == iterator && sfx instanceof Sound && sound) sfx.play(); //loadBuffer(bufferTime, false); validating = false; } final CoalescedThreadsMonitor monitor1 = validateMonitor; synchronized(monitor1.getMonitor(false)) { monitor1.notifyAllOnMonitor(); } if(interrupt_) Thread.currentThread().interrupt(); } /**
  • Returns the current frame in an Image instance, seen by the current ImageObserver .
  • @see #obs
  • @return the current frame image instance
  • /
public Image getImage(Component obs) throws InterruptedException { return getCurrentSprite().getImage(obs); } /**
  • Returns all the Sprites frames objects of this animation.
  • @discussion (comprehensive description)
  • @return a synchronized frames map view
  • /
public Map<Integer, Sprite> getFrames() { return frames; } /** tells whether mirror transform is enabled
  • @see #getMirrorOrientation()
  • @return true or false*/
public boolean isMirrored() { return (mirror == NONE)?false:true; } /** returns the current mirror orientation or
  • @see #NONE
  • @return mirror orientation */
public int getMirrorOrientation() { return mirror; } /** tells whether the zoom transform is enabled
  • @see #getZoomValue()
  • @return true or false*/
public boolean isZoomed() { return (zoom == 1.0)?false:true; } /** returns current zoom value default is 1.0
  • @return zoom value (1.0 is no zoom)*/
public double getZoomValue() { return zoom; } /***/ public void setFrameRate(int delay) { if(delay < 0) throw new NullPointerException("framerate delay must be different from Zero!"); frameRate = delay; } /***/ public long getStartTime() { return start; } /***/ private transient Thread play = null; /**
  • Plays this animation. Actually iterator is incremented or decreased, depending on reverse status.
  • if it is called more than once it will safely manage to get it to continue (start time is updated!) even if it has been stopped or paused.
  • The sound FX will be played when the iterator comes to set frame index
  • @see #sfx_frame
  • @see #setSfx(String, int)
  • /
public void play() { System.out.println(start); start = adjustStartTime(); switch(status()) { case PAUSED: System.out.println("resumed>"); case STOPPED: rewind(); timer = new Timer(23, new ActionListener() { public void actionPerformed(ActionEvent e) { if(play instanceof Thread) if(play.isAlive()) return; Thread t = play = new Thread(validateMonitor, Animation.this, "T-Animation-Play"); t.setDaemon(false); t.setPriority(Thread.MAX_PRIORITY); t.start(); }}); timer.start(); break; case PLAYING: break; default: break; } System.out.println("play>"); } /**
  • Pauses this animation. Iterator stays as it is at the time. Timer is stopped.
  • /
public void pause() { if(timer != null) timer.stop(); System.out.println("pause||"); } /**
  • Stops the animation. Iterator will be reset. Timer is cancelled.
  • /
public void stop() { pause(); timer = null; System.out.println("stop[]"); } /** returns iterator for desired operation-mode or -1
  • @param mode operation-mode
  • @see #NEXT
  • @see #PREVIOUS *
  • @param position where to get the iterator from
  • @return the returned iterator*/
private int getIteratorValue(int mode, int position) { int i = position; switch(mode) { case NEXT: if(reverse) { if(i > 0) i--; else i = length - 1; } else { if(i < length - 1) i++; else i = 0; } break; case PREVIOUS: if(reverse) { if(i < length - 1) i++; else i = 0; } else { if(i > 0) i--; else i = length - 1; } break; default: break; } return i; } /**
  • Invokes validate() and moves iterator to next frame index.
*
  • @return next frame sprite
  • @see #getIteratorValue(int, int)
  • @discussion (comprehensive description)
  • /
public Sprite next() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); iterator = getIteratorValue(NEXT, iterator); return getCurrentSprite(); } } /**
  • Invokes validate() and moves iterator to previous frame index.
*
  • @return previous frame sprite
  • @see #getIteratorValue(int, int)
  • @discussion (comprehensive description)
  • /
public Sprite previous() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); iterator = getIteratorValue(PREVIOUS, iterator); return getCurrentSprite(); } } /**
  • Rewinds to begin index. Invokes validate().
*
  • @discussion (comprehensive description)
  • /
public void rewind() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); if(reverse) iterator = length - 1; else iterator = 0; } } /**
  • Forwards to end index. Invokes validate()
*
  • @discussion (comprehensive description)
  • /
public void end() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); if(!reverse) iterator = length - 1; else iterator = 0; } } /** clears the cache resources
  • @return Thread id of that command
  • @see #waitForActivity(int)
  • /
public Integer clearResource() { int hash = 0; stop(); Thread t = new Thread(new Runnable() { public void run() { currentSprite = null; for(int i = 0; i < length; i++) unloadFrame(i); }}, "T-ClearResources"); bufferThreads.put(hash = t.hashCode(), t); t.setDaemon(false); t.setPriority(Thread.MAX_PRIORITY); t.start(); return hash; } /** caches the resources
  • @return Thread id of that command
  • @see #waitForActivity(int)
  • /
public Integer loadResource() { int hash = 0; stop(); Thread t = new Thread(new Runnable() { public void run() { for(int i = 0; i < length; i++) loadFrame(i); currentSprite = getCurrentSprite(); }}, "T-CacheResources"); bufferThreads.put(hash = t.hashCode(), t); t.setDaemon(false); t.setPriority(Thread.MAX_PRIORITY); t.start(); return hash; } /** cancels any running Thread of that animation, including the play timer */ public void cancel() { int pty = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); stopAllActivity(); timer = null; clearResource(); Thread.currentThread().setPriority(pty); } /** removes current frame from mapping
  • @deprecated */
public void remove() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); frames.remove(iterator); } } /** checks for next frame availability
  • @return true or false*/
public boolean hasNext() { final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); int next = (reverse)?-1:+1; return frames.containsKey(iterator + next); } } /**
  • waits for activity identified by argument. this method synchronizes on this Animation.
*
  • @param threadID thread hashCode of that activity
  • @see #clearResource()
  • @see #loadResource
  • @see #loadBuffer(long)
  • /
public void waitForActivity(int threadID) throws InterruptedException { Thread t = null; if((t = bufferThreads.get(threadID)) != null) { spm.buffer(t); if(t.isAlive()) { t.join(); } bufferThreads.remove(threadID); t = null; } } /** waits for all running threads to terminate in this animation
  • @see #waitForActivity(int)*/
public void waitForAllActivity() throws InterruptedException { Set<Integer> set = bufferThreads.keySet(); synchronized(bufferThreads) { for(Iterator<Integer> i = set.iterator(); i.hasNext(); ) waitForActivity(i.next()); } } /** checks for any running threads
  • @see #checkActivity(int)
  • @return true or false whether any thread is running or not */
public boolean checkAllActivity() { Set<Integer> set = bufferThreads.keySet(); synchronized(bufferThreads) { for(Iterator<Integer> i = set.iterator(); i.hasNext(); ) { if(checkActivity(i.next())) return true; } return false; } } /***/ protected void paintComponent(Graphics g) { try { draw(this, (Graphics2D)g); } catch (InterruptedException e) { e.printStackTrace(); } } /**
  • checks for a running thread
*
  • @param threadID hashCode id
  • @return true or false whether the thread is running or not
  • @see #clearResource()
  • @see #loadResource()
  • @see #loadBuffer(long)
  • /
public boolean checkActivity(int threadID) { Thread t = null; if((t = bufferThreads.get(threadID)) != null) { if(t.isInterrupted()) bufferThreads.remove(threadID); return t.isAlive(); } return false; } /** stops all running threads excluding the play timer*/ public void stopAllActivity() { stop(); packMonitor.cancelAll(true); validateMonitor.cancelAll(true); paintMonitor.cancelAll(true); bufferMonitor.cancelAll(true); Set<Integer> set = bufferThreads.keySet(); synchronized(bufferThreads) { for(Iterator<Integer> i = set.iterator(); i.hasNext(); ) stopActivity(i.next()); } } /**
  • stops a running thread
*
  • @param threadID hashCode id
  • @see #clearResource()
  • @see #loadResource()
  • @see #loadBuffer(long)
  • /
public void stopActivity(int threadID) { int currentPty = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Thread t = null; if((t = bufferThreads.get(threadID)) != null) { t.interrupt(); bufferThreads.remove(threadID); } Thread.currentThread().setPriority(currentPty); } /** change cache capacity to allow more frames to be mapped
  • @deprecated
  • @see SpritesCacheManager#setCacheCapacity(int)*/
public void setCacheCapacity(int n) { spm.setListCapacity(n); } /***/ public int getCacheCapacity() { return spm.getListCapacity(); } /** unused */ public void pack() { throw new UnsupportedOperationException("Animations don't need to pack. call super.pack"); } /***/ private transient Thread buffer = null; /***/ private transient boolean buffering = false; /** loads the given timeframe in cache w/o waiting for it to terminate
  • @param time timeframe to load in ms
  • @return Thread instance of that command */
public int loadBuffer(long time, boolean pforce) { if(buffer instanceof Thread) if(buffer.isAlive()) return buffer.hashCode(); final boolean force = pforce; final int n = (int)((float)time / (float)frameRate); final int offset; final CoalescedThreadsMonitor monitor = imageSynch; synchronized(monitor.getMonitor(false)) { monitor.notifyOnMonitor(); offset = iterator; } Thread t = buffer = new Thread(bufferMonitor, new Runnable() { public void run() { boolean interrupt_ = false; if(force) stopAllActivity(); else { try{ waitForAllActivity(); } catch(InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } bufferThreads.put(Thread.currentThread().hashCode(), Thread.currentThread()); System.out.println(frameRate + "fps Buffering " + bufferTime + " -> " + n + "..."); spm.ensureListCapacity(spm.getListCapacity() + n); int i = getIteratorValue(NEXT, offset); for(int bF = 0; bF < n; bF++) { final CoalescedThreadsMonitor monitor0 = packMonitor; synchronized(monitor0.getMonitor(false)) { if(packing) System.out.println("Animation buffer's waitin' for packing..."); while(packing) { System.out.print("."); try { monitor0.waitOnMonitor(10); } catch(InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } buffering = true; System.out.print("OK\n\r"); Sprite sp; sp = loadFrame(i); if(sp != null) { bufferMap.put(i, new PhantomReference(sp, spm._cacheBack)); System.out.println("[load]"); } i = getIteratorValue(NEXT, i); buffering = false; monitor0.notifyOnMonitor(); } final CoalescedThreadsMonitor monitor1 = bufferMonitor; synchronized(monitor1.getMonitor(false)) { monitor1.notifyAllOnMonitor(); } } /*int j = getIteratorValue(NEXT, offset); for(int bF = 0; bF < n; bF++) { try { mt.waitForID(0); mt.waitForID((reverse)?length - j:j); j = getIteratorValue(NEXT, j); } catch (InterruptedException ex) { ex.printStackTrace(); interrupt_ = true; break; } }*/ if(interrupt_) Thread.currentThread().interrupt(); }}, "T-loadbuffer"); t.setPriority(Thread.MAX_PRIORITY); t.setDaemon(false); t.start(); return t.hashCode(); } /**
  • changes bufferMap timeframe in the play timer
*
  • @param time frame-time to load in ms
  • /
public void setBufferTime(long time) { bufferTime = time; } /**
  • returns current bufferMap timeframe in ms
*
  • @return bufferMap timeframe in ms
  • /
public long getBufferTime() { return bufferTime; } /**
  • returns current buffered timeframe
*
  • @return current timeframe buffered in percent
  • @see #bufferMap
  • /
public double getBufferAlloc() { return (double)((float)bufferMap.size()/(float)length * 100.0f); } /**
  • returns ceche ReferenceQueue
*
  • @return ReferenceQueue in_cacheBack poll for objects on it
  • @see SpritesCacheManager#cacheBack
  • /
public ReferenceQueue getCacheBack() { return spm._cacheBack; } /** enables transform. if it is disabled no transform will be made (no mirroring nor zooming)
  • @see #setFlipEnabled(boolean, int)
  • @see #setZoomEnabled(boolean, double)
  • @param transform enable:disable*/
public void setTransformEnabled(boolean transform) { this.transform = transform; } /** plays the associated Sound in this Animation
  • @return true or false, whether the sound played or not*/
boolean playSfx() { if(sfx instanceof Sound) { sfx.play(); return true; } else return false; } /***/ public void setThreadGroup(ThreadGroup tg) { super.setThreadGroup(tg); if(bufferMonitor instanceof ThreadGroup) bufferMonitor.interrupt(); if(tg instanceof ThreadGroup) { bufferMonitor = new CoalescedThreadsMonitor(tg, "TG-" + getClass().getName() + "-buffer " + base); } else { bufferMonitor = new CoalescedThreadsMonitor("TG-" + getClass().getName() + "-buffer " + base); } bufferMonitor.setCoalesceEnabled(false); bufferMonitor.setMaxPriority(paintMonitor.getMaxPriority() - 1); } }

Conclusion :


le code source ici présenté peut être accompagné des sources distribuées en CVS sur sourceforge.net projet sf3jswing

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.