001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package example.photoalbum;
033:
034: import java.util.Vector;
035:
036: import javax.microedition.lcdui.*;
037:
038: /**
039: * This PhotoFrame provides the picture frame and drives the animation
040: * of the frames and the picture. It handles the starting and stopping
041: * of the Animation and contains the Thread used to do
042: * the timing and requests that the Canvas be repainted
043: * periodically.
044: * It controls the border style and animation speed.
045: */
046: class PhotoFrame extends Canvas implements Runnable {
047: /**
048: * Mapping of speed values to delays in ms.
049: * Indices map to those in the speed ChoiceGroup in the options form.
050: * The indices are: 0 = stop, 1 = slow, 2 = medium, 3 = fast.
051: * @see setSpeed
052: */
053: private static final int[] speeds = { 999999999, 500, 250, 100, 0 };
054:
055: /** border style */
056: private int style;
057:
058: /** animation speed set */
059: private int speed;
060:
061: /** Vector of images to display */
062: private Vector images;
063:
064: /** Index of next image to display */
065: private int index;
066:
067: /** X offset of image in frame */
068: private int imageX;
069:
070: /** X offset of image in frame */
071: private int imageY;
072:
073: /** Width and height of image */
074: private int imageWidth;
075:
076: /** Width and height of image */
077: private int imageHeight;
078:
079: /** Thread used for triggering repaints */
080: private Thread thread;
081:
082: /** buffer image of the screen */
083: private Image image;
084:
085: /** Pattern image used for border */
086: private Image bimage;
087:
088: /** Time of most recent paint */
089: private long paintTime;
090:
091: /** Time of most recent frame rate report */
092: private long statsTime;
093:
094: /** Number of frames since last frame rate report */
095: int frameCount;
096:
097: /** Last reported frame rate (for re-paint) */
098: int frameRate;
099:
100: /**
101: * Create a new PhotoFrame.
102: * Create an offscreen mutable image into which the border is drawn.
103: * Border style is none (0).
104: * Speed is stopped (0) until set.
105: */
106: PhotoFrame() {
107: image = Image.createImage(getWidth(), getHeight());
108: setStyle(0);
109: setSpeed(0);
110: }
111:
112: /**
113: * Set the array of images to be displayed.
114: * Update the width and height of the image and draw
115: * the border to fit around it in the offscreen image.
116: * @param images a vector of images to be displayed.
117: */
118: void setImages(Vector images) {
119: this .images = images;
120:
121: if (images.size() > 0) {
122: Image image = (Image) images.elementAt(0);
123: imageWidth = image.getWidth();
124: imageHeight = image.getHeight();
125: } else {
126: imageWidth = 0;
127: imageHeight = 0;
128: }
129:
130: index = 0;
131: imageX = (getWidth() - imageWidth) / 2;
132: imageY = (getHeight() - imageHeight) / 2;
133: genFrame(style, imageX, imageY, imageWidth, imageHeight);
134: }
135:
136: /**
137: * Advance to the next image and wrap around if necessary.
138: */
139: void next() {
140: if ((images == null) || (++index >= images.size())) {
141: index = 0;
142: }
143: }
144:
145: /**
146: * Back up to the previous image.
147: * Wrap around to the end if at the beginning.
148: */
149: void previous() {
150: if ((images != null) && (--index < 0)) {
151: index = images.size() - 1;
152: } else {
153: index = 0;
154: }
155: }
156:
157: /**
158: * Reset the PhotoFrame so it holds minimal resources.
159: * The animation thread is stopped.
160: */
161: void reset() {
162: images = null;
163: thread = null;
164: }
165:
166: /**
167: * Handle key events. FIRE events toggle between
168: * running and stopped. LEFT and RIGHT key events
169: * when stopped show the previous or next image.
170: * @param keyCode of the key pressed
171: */
172: protected void keyPressed(int keyCode) {
173: int action = getGameAction(keyCode);
174:
175: switch (action) {
176: case RIGHT:
177:
178: if (thread == null) {
179: next();
180: repaint();
181: }
182:
183: break;
184:
185: case LEFT:
186:
187: if (thread == null) {
188: previous();
189: repaint();
190: }
191:
192: break;
193:
194: case FIRE:
195:
196: // Use FIRE to toggle the activity of the thread
197: if (thread == null) {
198: thread = new Thread(this );
199: thread.start();
200: } else {
201: synchronized (this ) {
202: // Wake up the thread to change the timing
203: this .notify();
204: }
205:
206: // Shouldn't be in synchronized block
207: thread = null;
208: }
209:
210: break;
211: }
212: }
213:
214: /**
215: * Handle key repeat events as regular key events.
216: * @param keyCode of the key repeated
217: */
218: protected void keyRepeated(int keyCode) {
219: keyPressed(keyCode);
220: }
221:
222: /**
223: * Set the animation speed.
224: * Speed:
225: * <OL>
226: * <LI>0 = stop
227: * <LI>1 = slow
228: * <LI>2 = medium
229: * <LI>3 = fast
230: * <LI>4 = unlimited
231: * </OL>
232: * @param speed speedo of animation 0-3;
233: */
234: void setSpeed(int speed) {
235: this .speed = speed;
236: statsTime = 0;
237: }
238:
239: /**
240: * Get the speed at which animation occurs.
241: * @return the current speed.
242: * @see setSpeed
243: */
244: int getSpeed() {
245: return speed;
246: }
247:
248: /**
249: * Set the frame style.
250: * Recreate the photo frame image from the current style
251: * and location and size
252: * <p>
253: * Style:
254: * <OL>
255: * <LI> Style 0: No border is drawn.
256: * <LI> Style 1: A simple border is drawn
257: * <LI> Style 2: The border is outlined and an image
258: * is created to tile within the border.
259: * </OL>
260: * @param style the style of the border; 0 = none, 1 = simple,
261: * 2 = fancy.
262: */
263: void setStyle(int style) {
264: this .style = style;
265: genFrame(style, imageX, imageY, imageWidth, imageHeight);
266: }
267:
268: /**
269: * Get the style being used for borders.
270: * @return the style.
271: */
272: int getStyle() {
273: return style;
274: }
275:
276: /**
277: * Notified when Canvas is made visible.
278: * If there is more than one image to display
279: * create the thread to run the animation timing.
280: */
281: protected void showNotify() {
282: if ((images != null) && (images.size() > 1)) {
283: thread = new Thread(this );
284: thread.start();
285: }
286: }
287:
288: /**
289: * Notified when the Canvas is no longer visible.
290: * Signal the running Thread that it should stop.
291: */
292: protected void hideNotify() {
293: thread = null;
294: }
295:
296: /**
297: * Return true if the specified rectangle does not intersect
298: * the clipping rectangle of the graphics object. If it returns
299: * true then the object must be drawn otherwise it would be clipped
300: * completely.
301: * The checks are done
302: * @param g the Graphics context to check.
303: * @param x the x value of the upper left corner of the rectangle
304: * @param y the y value of the upper left corner of the rectangle
305: * @param w the width of the rectangle
306: * @param h the height of the rectangle
307: * @return true if the rectangle intersects the clipping region
308: */
309: boolean intersectsClip(Graphics g, int x, int y, int w, int h) {
310: int cx = g.getClipX();
311:
312: if ((x + w) <= cx) {
313: return false;
314: }
315:
316: int cw = g.getClipWidth();
317:
318: if (x > (cx + cw)) {
319: return false;
320: }
321:
322: int cy = g.getClipY();
323:
324: if ((y + h) <= cy) {
325: return false;
326: }
327:
328: int ch = g.getClipHeight();
329:
330: if (y > (cy + ch)) {
331: return false;
332: }
333:
334: return true;
335: }
336:
337: /**
338: * Runs the animation and makes the repaint requests.
339: * The thread will exit when it is no longer the current
340: * Animation thread.
341: */
342: public void run() {
343: Thread mythread = Thread.currentThread();
344: long scheduled = System.currentTimeMillis();
345: statsTime = scheduled;
346: paintTime = scheduled;
347: frameCount = 0;
348: frameRate = 0;
349:
350: /*
351: * The following code was changed to fix bug 4918599.
352: * The bug is caused by a deadlock caused by
353: * bad-designed use of synchronized blocks in demo.
354: */
355: while (thread == mythread) {
356: // Update when the next frame should be drawn
357: // and compute the delta till then
358: scheduled += speeds[speed];
359:
360: long delta = scheduled - paintTime;
361:
362: if (delta > 0) {
363: synchronized (this ) {
364: try {
365: this .wait(delta);
366: } catch (InterruptedException e) {
367: }
368: }
369: }
370:
371: // Advance and repaint the screen
372: next();
373: repaint();
374: serviceRepaints();
375: }
376: }
377:
378: /**
379: * Paint is called whenever the canvas should be redrawn.
380: * It clears the canvas and draws the frame and the current
381: * current frame from the animation.
382: * @param g the Graphics context to which to draw
383: */
384: protected void paint(Graphics g) {
385: paintTime = System.currentTimeMillis();
386:
387: if (image != null) {
388: // Draw the frame unless only the picture is being re-drawn
389: // This is the inverse of the usual clip check.
390: int cx = 0;
391:
392: // Draw the frame unless only the picture is being re-drawn
393: // This is the inverse of the usual clip check.
394: int cy = 0;
395:
396: // Draw the frame unless only the picture is being re-drawn
397: // This is the inverse of the usual clip check.
398: int cw = 0;
399:
400: // Draw the frame unless only the picture is being re-drawn
401: // This is the inverse of the usual clip check.
402: int ch = 0;
403:
404: if (((cx = g.getClipX()) < imageX)
405: || ((cy = g.getClipY()) < imageY)
406: || ((cx + (cw = g.getClipWidth())) > (imageX + imageWidth))
407: || ((cy + (ch = g.getClipHeight())) > (imageY + imageHeight))) {
408: g.drawImage(image, 0, 0, Graphics.LEFT | Graphics.TOP);
409:
410: if (frameRate > 0) {
411: g.fillRect(0, getHeight(), 60, 20);
412: g.drawString("FPS = " + frameRate, 0, getHeight(),
413: Graphics.BOTTOM | Graphics.LEFT);
414: }
415: }
416:
417: // Draw the image if it intersects the clipping region
418: if ((images != null)
419: && (index < images.size())
420: && intersectsClip(g, imageX, imageY, imageWidth,
421: imageHeight)) {
422: g.drawImage((Image) images.elementAt(index), imageX,
423: imageY, Graphics.LEFT | Graphics.TOP);
424: }
425:
426: frameCount++;
427:
428: // Update Frame rate
429: int delta = (int) (paintTime - statsTime);
430:
431: if ((delta > 1000) && (delta < 10000)) {
432: frameRate = (((frameCount * 1000) + 500) / delta);
433: frameCount = 0;
434: statsTime = paintTime;
435: repaint(); // queue full repaint to display frame rate
436: }
437: }
438: }
439:
440: /**
441: * Paint the photo frame into the buffered screen image.
442: * This will avoid drawing each of its parts on each repaint.
443: * Paint will only need to put the image into the frame.
444: * @param style the style of frame to draw.
445: * @param x the x offset of the image.
446: * @param y the y offset of the image
447: * @param width the width of the animation image
448: * @param height the height of the animation image
449: */
450: private void genFrame(int style, int x, int y, int width, int height) {
451: Graphics g = image.getGraphics();
452:
453: // Clear the entire image to white
454: g.setColor(0xffffff);
455: g.fillRect(0, 0, image.getWidth() + 1, image.getHeight() + 1);
456:
457: // Set the origin of the image and paint the border and image.
458: g.translate(x, y);
459: paintBorder(g, style, width, height);
460: }
461:
462: /**
463: * Draw a border of the selected style.
464: * @param g graphics context to which to draw.
465: * @param style of the border to display
466: * @param w the width reserved for the image
467: * @param h the height reserved of the image
468: * @see setStyle
469: */
470: private void paintBorder(Graphics g, int style, int w, int h) {
471: if (style == 1) {
472: g.setGrayScale(128);
473: g.drawRect(-1, -1, w + 1, h + 1);
474: g.drawRect(-2, -2, w + 3, h + 3);
475: }
476:
477: if (style == 2) {
478: // Draw fancy border with image between outer and inner rectangles
479: if (bimage == null) {
480: bimage = genBorder(); // Generate the border image
481: }
482:
483: int bw = bimage.getWidth();
484: int bh = bimage.getHeight();
485: int i;
486: // Draw the inner and outer solid border
487: g.setGrayScale(128);
488: g.drawRect(-1, -1, w + 1, h + 1);
489: g.drawRect(-bw - 2, -bh - 2, w + (bw * 2) + 3, h + (bh * 2)
490: + 3);
491:
492: // Draw it in each corner
493: g.drawImage(bimage, -1, -1, Graphics.BOTTOM
494: | Graphics.RIGHT);
495: g.drawImage(bimage, -1, h + 1, Graphics.TOP
496: | Graphics.RIGHT);
497: g.drawImage(bimage, w + 1, -1, Graphics.BOTTOM
498: | Graphics.LEFT);
499: g.drawImage(bimage, w + 1, h + 1, Graphics.TOP
500: | Graphics.LEFT);
501:
502: // Draw the embedded image down left and right sides
503: for (i = ((h % bh) / 2); i < (h - bh); i += bh) {
504: g.drawImage(bimage, -1, i, Graphics.RIGHT
505: | Graphics.TOP);
506: g.drawImage(bimage, w + 1, i, Graphics.LEFT
507: | Graphics.TOP);
508: }
509:
510: // Draw the embedded image across the top and bottom
511: for (i = ((w % bw) / 2); i < (w - bw); i += bw) {
512: g.drawImage(bimage, i, -1, Graphics.LEFT
513: | Graphics.BOTTOM);
514: g.drawImage(bimage, i, h + 1, Graphics.LEFT
515: | Graphics.TOP);
516: }
517: }
518: }
519:
520: /**
521: * Create an image for the border.
522: * The border consists of a simple "+" drawn in a 5x5 image.
523: * Fill the image with white and draw the "+" as magenta.
524: * @return the image initialized with the pattern
525: */
526: private Image genBorder() {
527: Image image = Image.createImage(5, 5);
528: Graphics g = image.getGraphics();
529: g.setColor(255, 255, 255);
530: g.fillRect(0, 0, 5, 5);
531: g.setColor(128, 0, 255);
532: g.drawLine(2, 1, 2, 3); // vertical
533: g.drawLine(1, 2, 3, 2); // horizontal
534:
535: return image;
536: }
537: }
|