001: /*
002: * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.animation;
032:
033: import java.awt.Color;
034: import java.util.Collections;
035: import java.util.Iterator;
036: import java.util.LinkedList;
037: import java.util.List;
038: import java.util.Random;
039:
040: /**
041: * This class consists only of static methods that construct and operate on
042: * {@link AnimationFunction}s.
043: *
044: * @author Karsten Lentzsch
045: * @version $Revision: 1.1 $
046: *
047: * @see AnimationFunction
048: */
049: public final class AnimationFunctions {
050:
051: /**
052: * A constant {@link AnimationFunction} that returns
053: * <code>1</code> all the time.
054: */
055: public static final AnimationFunction ONE = constant(
056: Integer.MAX_VALUE, new Float(1.0f));
057:
058: /**
059: * A constant {@link AnimationFunction} that returns
060: * <code>0.0f</code> all the time.
061: */
062: public static final AnimationFunction ZERO = constant(
063: Integer.MAX_VALUE, new Float(0.0f));
064:
065: private AnimationFunctions() {
066: // Overrides the default constructor; prevents instantiation.
067: }
068:
069: /**
070: * Creates and returns an animation function that returns time-based
071: * sRGB colors that are built from a given base color and
072: * an animation function of alpha values.
073: * <p>
074: * Useful for fading effects.
075: *
076: * @param f the animation function of alpha values
077: * @param baseColor the base color
078: * @return an animation function of colors
079: */
080: public static AnimationFunction alphaColor(AnimationFunction f,
081: Color baseColor) {
082: return new AlphaColorAnimationFunction(f, baseColor);
083: }
084:
085: /**
086: * Creates a time-based function that wraps the given Float-based animation
087: * function to return the corresponding float values.
088: *
089: * @param f the underlying animation function of floats
090: * @return an animation function the returns Float objects
091: */
092: public static FloatFunction asFloat(AnimationFunction f) {
093: return new FloatFunction(f);
094: }
095:
096: /**
097: * Concatenates the fiven animation functions and returns a compound
098: * animation function that represents the concatenation.
099: *
100: * @param first the concatenation's first AnimationFunction
101: * @param second the concatenation's second AnimationFunction
102: * @return the concatenated animation function
103: */
104: public static AnimationFunction concat(AnimationFunction first,
105: AnimationFunction second) {
106: List sequence = new LinkedList();
107: sequence.add(first);
108: sequence.add(second);
109: return new SequencedAnimationFunction(sequence);
110: }
111:
112: /**
113: * Creates and returns an animation function that returns a constant value
114: * over the given duration.
115: *
116: * @param duration the function's duration
117: * @param value the Object that will be returned all the time
118: * @return a constant animation function
119: */
120: public static AnimationFunction constant(long duration, Object value) {
121: return discrete(duration, new Object[] { value });
122: }
123:
124: /**
125: * Creates and returns a discrete animation function for the
126: * given duration and values. The values are equally distributed
127: * over the duration.
128: *
129: * @param duration the function's duration
130: * @param values an array of discrete result values
131: * @return a discrete animation function
132: */
133: public static AnimationFunction discrete(long duration,
134: Object[] values) {
135: return discrete(duration, values, null);
136: }
137:
138: /**
139: * Creates and returns a discrete animation function for the given duration,
140: * values and interpolation key times.
141: *
142: * @param duration the function's duration
143: * @param values an array of discrete result values
144: * @param keyTimes an array of key times used to distribute the
145: * result values over the time
146: * @return a discrete animation function
147: */
148: public static AnimationFunction discrete(long duration,
149: Object[] values, float[] keyTimes) {
150: return new InterpolatedAnimationFunction(duration, values,
151: keyTimes, InterpolatedAnimationFunction.MODE_DISCRETE);
152: }
153:
154: /**
155: * Creates and returns a linear animation function for the given duration
156: * that returns Float in interval [from, from + by].
157: *
158: * @param duration the animation duration
159: * @param from the initial result value
160: * @param by the difference that is added to the initial value
161: * @return a linear animation function with values in [from, from + by]
162: */
163: public static AnimationFunction fromBy(long duration, float from,
164: float by) {
165: return fromTo(duration, from, from + by);
166: }
167:
168: /**
169: * Ceates and returns a linear animation function with the given duration.
170: * The function values are Floats in the intervall [from, to].
171: *
172: * @param duration the animation duration
173: * @param from the initial result value
174: * @param to the last result value
175: * @return a linear animation function with values in [from, to]
176: */
177: public static AnimationFunction fromTo(long duration, float from,
178: float to) {
179: return linear(duration, new Float[] { new Float(from),
180: new Float(to) });
181: }
182:
183: /**
184: * Creates and returns a linear animation function that is defined
185: * by an array of numeric values; these are distributed equally
186: * over the duration.
187: *
188: * @param duration the animation duration
189: * @param values an array of values
190: * @return a linear animation function for the given values
191: */
192: public static AnimationFunction linear(long duration,
193: Object[] values) {
194: return linear(duration, values, null);
195: }
196:
197: /**
198: * Creates and returns a linear animation function that is defined
199: * by an array of numeric values and an array of relative key times.
200: *
201: * @param duration the animation duration
202: * @param values an array of values
203: * @param keyTimes an array of key times used to distribute the
204: * result values over the time
205: * @return a linear animation function for the given values
206: */
207: public static AnimationFunction linear(long duration,
208: Object[] values, float[] keyTimes) {
209: return new InterpolatedAnimationFunction(duration, values,
210: keyTimes, InterpolatedAnimationFunction.MODE_LINEAR);
211: }
212:
213: /**
214: * Creates an <code>AnimationFunction</code> that maps times
215: * to instances of <code>Color</code>. The mapping is interpolated
216: * from an array of Colors using an array of key times.
217: *
218: * @param duration the duration of this animation function
219: * @param colors the colors to interpolate.
220: * @param keyTimes an array of key times used to distribute
221: * the result values over the time.
222: * @return An {@link AnimationFunction} that maps times to sRGB colors.
223: * This mapping is defined by an arry of <code>Color</code> values
224: * and a corresponding array of key times that is used to interpolate
225: * sub-AnimationFunction for the red, green, blue and alpha values.
226: */
227: public static AnimationFunction linearColors(long duration,
228: Color[] colors, float[] keyTimes) {
229: return new ColorFunction(duration, colors, keyTimes);
230: }
231:
232: /**
233: * Creates and returns an animation function that returns random values
234: * from the interval [min, max] with a given change probability.
235: *
236: * @param min the minimum result value
237: * @param max the maximum result value
238: * @param changeProbability the probability that the value changes
239: * @return an animation function with random values in [min, max]
240: */
241: public static AnimationFunction random(int min, int max,
242: float changeProbability) {
243: return new RandomAnimationFunction(min, max, changeProbability);
244: }
245:
246: /**
247: * Creates and returns an animation function that is defined
248: * by repeating the specified animation function.
249: *
250: * @param f the animation function to repeat
251: * @param repeatTime the time to repeat the function
252: * @return the repeated animation function
253: */
254: public static AnimationFunction repeat(AnimationFunction f,
255: long repeatTime) {
256: return new RepeatedAnimationFunction(f, repeatTime);
257: }
258:
259: /**
260: * Creates and returns an animation function that is defined
261: * by reverting the given animation function in time.
262: *
263: * @param f the animation function to reverse
264: * @return the reversed animation function
265: */
266: public static AnimationFunction reverse(AnimationFunction f) {
267: return new ReversedAnimationFunction(f);
268: }
269:
270: // Helper Classes *********************************************************
271:
272: /**
273: * Helper class for animation functions that answer translucent colors.
274: */
275: private static final class AlphaColorAnimationFunction implements
276: AnimationFunction {
277: private final Color baseColor;
278: private final AnimationFunction fAlpha;
279:
280: private AlphaColorAnimationFunction(AnimationFunction fAlpha,
281: Color baseColor) {
282: this .fAlpha = fAlpha;
283: this .baseColor = baseColor;
284: }
285:
286: public long duration() {
287: return fAlpha.duration();
288: }
289:
290: // Constructs colors from the sRGB color space.
291: public Object valueAt(long time) {
292: int alpha = ((Float) fAlpha.valueAt(time)).intValue();
293: return new Color(baseColor.getRed(), baseColor.getGreen(),
294: baseColor.getBlue(), alpha);
295: }
296: }
297:
298: /**
299: * Helper class for interpolating colors.
300: */
301: private static final class ColorFunction extends
302: AbstractAnimationFunction {
303:
304: /**
305: * Refers to an AnimationFunction of float values that maps
306: * a time to the red component of an sRGB color value.
307: */
308: private final AnimationFunctions.FloatFunction redFunction;
309:
310: /**
311: * Refers to an AnimationFunction of float values that maps
312: * a time to the green component of an sRGB color value.
313: */
314: private final AnimationFunctions.FloatFunction greenFunction;
315:
316: /**
317: * Refers to an AnimationFunction of float values that maps
318: * a time to the blue component of an sRGB color value.
319: */
320: private final AnimationFunctions.FloatFunction blueFunction;
321:
322: /**
323: * Refers to an AnimationFunction of float values that maps
324: * a time to the alpha value of an sRGB color value.
325: */
326: private final AnimationFunctions.FloatFunction alphaFunction;
327:
328: // Instance creation ******************************************************
329:
330: /**
331: * Creates an <code>AnimationFunction</code> that maps times
332: * to instances of <code>Color</code>. The mapping is interpolated
333: * from an array of Colors using an array of key times.
334: *
335: * @param duration the duration of this animation function
336: * @param colors the colors to interpolate.
337: * @param keyTimes an array of key times used to distribute
338: * the result values over the time.
339: */
340: private ColorFunction(long duration, Color[] colors,
341: float[] keyTimes) {
342: super (duration);
343: Float[] red = new Float[colors.length];
344: Float[] green = new Float[colors.length];
345: Float[] blue = new Float[colors.length];
346: Float[] alpha = new Float[colors.length];
347:
348: for (int i = 0; i < colors.length; i++) {
349: red[i] = new Float(colors[i].getRed());
350: green[i] = new Float(colors[i].getGreen());
351: blue[i] = new Float(colors[i].getBlue());
352: alpha[i] = new Float(colors[i].getAlpha());
353: }
354:
355: redFunction = AnimationFunctions.asFloat(AnimationFunctions
356: .linear(duration, red, keyTimes));
357: greenFunction = AnimationFunctions
358: .asFloat(AnimationFunctions.linear(duration, green,
359: keyTimes));
360: blueFunction = AnimationFunctions
361: .asFloat(AnimationFunctions.linear(duration, blue,
362: keyTimes));
363: alphaFunction = AnimationFunctions
364: .asFloat(AnimationFunctions.linear(duration, alpha,
365: keyTimes));
366: }
367:
368: // AnimationFunction Implementation ***************************************
369:
370: /**
371: * Returns the interpolated color for a given time in the valid
372: * time interval. This method is required to implement the
373: * AnimationFunction interface and just forwards to the type-safe
374: * counterpart <code>#colorValueAt</code>
375: *
376: * @param time the time used to determine the interpolated color
377: * @return the interpolated color for the given time
378: * @see ColorFunction#colorValueAt(long)
379: */
380: public Object valueAt(long time) {
381: return colorValueAt(time);
382: }
383:
384: /**
385: * Returns the interpolated color for a given time in the valid
386: * time interval.
387: *
388: * @param time the time used to determine the interpolated color.
389: * @return the interpolated color for the given time
390: * @see ColorFunction#valueAt(long)
391: */
392: private Color colorValueAt(long time) {
393: checkTimeRange(time);
394: return new Color((int) redFunction.valueAt(time),
395: (int) greenFunction.valueAt(time),
396: (int) blueFunction.valueAt(time),
397: (int) alphaFunction.valueAt(time));
398: }
399:
400: }
401:
402: /**
403: * Helper class that wraps a Float-based animation function to answer floats.
404: */
405: public static class FloatFunction {
406: private final AnimationFunction f;
407:
408: FloatFunction(AnimationFunction f) {
409: this .f = f;
410: }
411:
412: public long duration() {
413: return f.duration();
414: }
415:
416: public float valueAt(long time) {
417: return ((Number) f.valueAt(time)).floatValue();
418: }
419: }
420:
421: /**
422: * Helper class for interpolation based animation functions.
423: */
424: private static final class InterpolatedAnimationFunction extends
425: AbstractAnimationFunction {
426: static final int MODE_DISCRETE = 1;
427: static final int MODE_LINEAR = 2;
428: private final float[] keyTimes;
429: private final int mode;
430:
431: /* Left for long winter nights in northern Germany.
432: static final int MODE_PACED = 4;
433: static final int MODE_SPLINE = 8;
434: */
435:
436: private final Object[] values;
437:
438: private InterpolatedAnimationFunction(long duration,
439: Object[] values, float[] keyTimes, int mode) {
440: super (duration);
441: this .values = values;
442: this .keyTimes = keyTimes;
443: this .mode = mode;
444: checkValidKeyTimes(values.length, keyTimes);
445: }
446:
447: private void checkValidKeyTimes(int valuesLength,
448: float[] theKeyTimes) {
449: if (theKeyTimes == null)
450: return;
451:
452: if (valuesLength < 2 || valuesLength != theKeyTimes.length)
453: throw new IllegalArgumentException(
454: "The values and key times arrays must be non-empty and must have equal length.");
455:
456: for (int index = 0; index < theKeyTimes.length - 2; index++) {
457: if (theKeyTimes[index] >= theKeyTimes[index + 1])
458: throw new IllegalArgumentException(
459: "The key times must be increasing.");
460: }
461: }
462:
463: private Object discreteValueAt(long time) {
464: return values[indexAt(time, values.length)];
465: }
466:
467: private int indexAt(long time, int intervalCount) {
468: long duration = duration();
469: // Gleichlange Zeitabschnitte
470: if (keyTimes == null) {
471: return (int) (time * intervalCount / duration);
472: }
473: for (int index = keyTimes.length - 1; index > 0; index--) {
474: if (time >= duration * keyTimes[index])
475: return index;
476: }
477: return 0;
478: }
479:
480: /**
481: * Currently we provide only linear interpolations that are based on floats.
482: *
483: * @param value1 the first interpolation key point
484: * @param value2 the second interpolation key point
485: * @param time the time to get an interpolated value for
486: * @param duration the duration of the whole animation
487: * @return the interpolated value at the given time
488: */
489: private Object interpolateLinear(Object value1, Object value2,
490: long time, long duration) {
491: float f1 = ((Number) value1).floatValue();
492: float f2 = ((Number) value2).floatValue();
493: float value = f1 + (f2 - f1) * time / duration;
494: return new Float(value);
495: }
496:
497: private Object linearValueAt(long time) {
498: int segments = values.length - 1;
499: int beginIndex = indexAt(time, segments);
500: int endIndex = beginIndex + 1;
501: long lastTime = duration() - 1;
502: long beginTime = keyTimes == null ? beginIndex * lastTime
503: / segments
504: : (long) (keyTimes[beginIndex] * lastTime);
505: long endTime = keyTimes == null ? endIndex * lastTime
506: / segments : (long) (keyTimes[endIndex] * lastTime);
507:
508: return interpolateLinear(values[beginIndex],
509: values[endIndex], time - beginTime, endTime
510: - beginTime);
511: }
512:
513: public Object valueAt(long time) {
514: checkTimeRange(time);
515: switch (mode) {
516: case MODE_DISCRETE:
517: return discreteValueAt(time);
518:
519: case MODE_LINEAR:
520: return linearValueAt(time);
521:
522: default:
523: throw new IllegalStateException(
524: "Unsupported interpolation mode.");
525: }
526: }
527: }
528:
529: /**
530: * Helper class for animation function that answer random values.
531: */
532: private static final class RandomAnimationFunction implements
533: AnimationFunction {
534: private final float changeProbability;
535: private final int max;
536: private final int min;
537: private final Random random;
538:
539: private Object value;
540:
541: private RandomAnimationFunction(int min, int max,
542: float changeProbability) {
543: this .random = new Random();
544: this .min = min;
545: this .max = max;
546: this .changeProbability = changeProbability;
547: }
548:
549: public long duration() {
550: return Integer.MAX_VALUE;
551: }
552:
553: public Object valueAt(long time) {
554: if ((value == null)
555: || (random.nextFloat() < changeProbability)) {
556: value = new Integer(min + random.nextInt(max - min));
557: }
558: return value;
559: }
560: }
561:
562: /**
563: * Helper class used to repeat or sequence an animation function.
564: */
565: private static final class RepeatedAnimationFunction extends
566: AbstractAnimationFunction {
567: private final AnimationFunction f;
568: private final long simpleDuration;
569:
570: private RepeatedAnimationFunction(AnimationFunction f,
571: long repeatTime) {
572: super (repeatTime);
573: this .f = f;
574: this .simpleDuration = f.duration();
575: }
576:
577: public Object valueAt(long time) {
578: return f.valueAt(time % simpleDuration);
579: }
580: }
581:
582: /**
583: * Helper class for reversing an animation function.
584: */
585: private static final class ReversedAnimationFunction extends
586: AbstractAnimationFunction {
587: private final AnimationFunction f;
588:
589: private ReversedAnimationFunction(AnimationFunction f) {
590: super (f.duration());
591: this .f = f;
592: }
593:
594: public Object valueAt(long time) {
595: return f.valueAt(duration() - time);
596: }
597: }
598:
599: /**
600: * Helper class to compose an animation functions from a sequences of such functions.
601: */
602: private static final class SequencedAnimationFunction implements
603: AnimationFunction {
604: private final List functions;
605:
606: private SequencedAnimationFunction(List functions) {
607: this .functions = Collections.unmodifiableList(functions);
608: if (this .functions.isEmpty())
609: throw new IllegalArgumentException(
610: "The list of functions must not be empty.");
611: }
612:
613: public long duration() {
614: long cumulatedDuration = 0;
615: for (Iterator i = functions.iterator(); i.hasNext();) {
616: AnimationFunction f = (AnimationFunction) i.next();
617: cumulatedDuration += f.duration();
618: }
619: return cumulatedDuration;
620: }
621:
622: public Object valueAt(long time) {
623: if (time < 0)
624: throw new IllegalArgumentException(
625: "The time must be positive.");
626:
627: long begin = 0;
628: long end;
629: for (Iterator i = functions.iterator(); i.hasNext();) {
630: AnimationFunction f = (AnimationFunction) i.next();
631: end = begin + f.duration();
632: if (time < end)
633: return f.valueAt(time - begin);
634: begin = end;
635: }
636: throw new IllegalArgumentException(
637: "The time must be smaller than the total duration.");
638: }
639: }
640:
641: }
|