001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on August 15, 2004, 2:51 AM
035 */
036
037package com.kitfox.svg.animation;
038
039import com.kitfox.svg.SVGConst;
040import com.kitfox.svg.SVGElement;
041import com.kitfox.svg.SVGException;
042import com.kitfox.svg.SVGLoaderHelper;
043import com.kitfox.svg.animation.parser.AnimTimeParser;
044import com.kitfox.svg.xml.ColorTable;
045import com.kitfox.svg.xml.StyleAttribute;
046import com.kitfox.svg.xml.XMLParseUtil;
047import java.awt.Color;
048import java.awt.geom.AffineTransform;
049import java.awt.geom.GeneralPath;
050import java.awt.geom.PathIterator;
051import java.util.logging.Level;
052import java.util.logging.Logger;
053import org.xml.sax.Attributes;
054import org.xml.sax.SAXException;
055
056
057/**
058 * Animate is a really annoying morphic tag that could represent a real value,
059 * a color or a path
060 *
061 * @author Mark McKay
062 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
063 */
064public class Animate extends AnimateBase implements AnimateColorIface
065{
066    public static final String TAG_NAME = "animate";
067    
068//    StyleAttribute retAttrib = new StyleAttribute
069    public static final int DT_REAL = 0;
070    public static final int DT_COLOR = 1;
071    public static final int DT_PATH = 2;
072    int dataType = DT_REAL;
073    
074    private double fromValue = Double.NaN;
075    private double toValue = Double.NaN;
076    private double byValue = Double.NaN;
077    private double[] valuesValue;
078    
079    private Color fromColor = null;
080    private Color toColor = null;
081
082    private GeneralPath fromPath = null;
083    private GeneralPath toPath = null;
084
085    /** Creates a new instance of Animate */
086    public Animate()
087    {
088    }
089
090    @Override
091    public String getTagName()
092    {
093        return TAG_NAME;
094    }
095
096    public int getDataType()
097    {
098        return dataType;
099    }
100    
101    @Override
102    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
103    {
104                //Load style string
105        super.loaderStartElement(helper, attrs, parent);
106
107        String strn = attrs.getValue("from");
108        if (strn != null)
109        {
110            if (XMLParseUtil.isDouble(strn))
111            {
112                fromValue = XMLParseUtil.parseDouble(strn); 
113            } 
114//            else if (attrs.getValue("attributeName").equals("d"))
115//            {
116//                fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
117//                dataType = DT_PATH;
118//            }
119            else
120            {
121                fromColor = ColorTable.parseColor(strn); 
122                if (fromColor == null)
123                {
124                    //Try path
125                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
126                    dataType = DT_PATH;
127                }
128                else dataType = DT_COLOR;
129            }
130        }
131
132        strn = attrs.getValue("to");
133        if (strn != null)
134        {
135            if (XMLParseUtil.isDouble(strn))
136            {
137                toValue = XMLParseUtil.parseDouble(strn); 
138            } 
139            else
140            {
141                toColor = ColorTable.parseColor(strn); 
142                if (toColor == null)
143                {
144                    //Try path
145                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
146                    dataType = DT_PATH;
147                }
148                else dataType = DT_COLOR;
149            }
150        }
151
152        strn = attrs.getValue("by");
153        try 
154        {
155            if (strn != null) byValue = XMLParseUtil.parseDouble(strn); 
156        } catch (Exception e) {}
157
158        strn = attrs.getValue("values");
159        try 
160        {
161            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn); 
162        } catch (Exception e) {}
163    }
164    
165    /**
166     * Evaluates this animation element for the passed interpolation time.  Interp
167     * must be on [0..1].
168     */
169    public double eval(double interp)
170    {
171        boolean fromExists = !Double.isNaN(fromValue);
172        boolean toExists = !Double.isNaN(toValue);
173        boolean byExists = !Double.isNaN(byValue);
174        boolean valuesExists = valuesValue != null;
175        
176        if (valuesExists)
177        {
178            double sp = interp * valuesValue.length;
179            int ip = (int)sp;
180            double fp = sp - ip;
181            
182            int i0 = ip;
183            int i1 = ip + 1;
184            
185            if (i0 < 0) return valuesValue[0];
186            if (i1 >= valuesValue.length) return valuesValue[valuesValue.length - 1];
187            return valuesValue[i0] * (1 - fp) + valuesValue[i1] * fp;
188        }
189        else if (fromExists && toExists)
190        {
191            return toValue * interp + fromValue * (1.0 - interp);
192        }
193        else if (fromExists && byExists)
194        {
195            return fromValue + byValue * interp;
196        }
197        else if (toExists && byExists)
198        {
199            return toValue - byValue * (1.0 - interp);
200        }
201        else if (byExists)
202        {
203            return byValue * interp;
204        }
205        else if (toExists)
206        {
207            StyleAttribute style = new StyleAttribute(getAttribName());
208            try
209            {
210                getParent().getStyle(style, true, false);
211            }
212            catch (SVGException ex)
213            {
214                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 
215                    "Could not get from value", ex);
216            }
217            double from = style.getDoubleValue();
218            return toValue * interp + from * (1.0 - interp);
219        }
220  
221        //Should not reach this line
222        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
223    }
224
225    public Color evalColor(double interp)
226    {
227        if (fromColor == null && toColor != null)
228        {
229            float[] toCol = new float[3];
230            toColor.getColorComponents(toCol);
231            return new Color(toCol[0] * (float)interp, 
232                toCol[1] * (float)interp, 
233                toCol[2] * (float)interp);
234        }
235        else if (fromColor != null && toColor != null)
236        {
237            float nInterp = 1 - (float)interp;
238            
239            float[] fromCol = new float[3];
240            float[] toCol = new float[3];
241            fromColor.getColorComponents(fromCol);
242            toColor.getColorComponents(toCol);
243            return new Color(fromCol[0] * nInterp + toCol[0] * (float)interp, 
244                fromCol[1] * nInterp + toCol[1] * (float)interp, 
245                fromCol[2] * nInterp + toCol[2] * (float)interp);
246        }
247        
248        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
249    }
250
251    public GeneralPath evalPath(double interp)
252    {
253        if (fromPath == null && toPath != null)
254        {
255            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
256            
257            GeneralPath midPath = new GeneralPath();
258            float[] coordsTo = new float[6];
259            
260            for (; !itTo.isDone(); itTo.next())
261            {
262                int segTo = itTo.currentSegment(coordsTo);
263                
264                switch (segTo)
265                {
266                    case PathIterator.SEG_CLOSE:
267                        midPath.closePath();
268                        break;
269                    case PathIterator.SEG_CUBICTO:
270                        midPath.curveTo(
271                                (float)(coordsTo[0] * interp), 
272                                (float)(coordsTo[1] * interp), 
273                                (float)(coordsTo[2] * interp), 
274                                (float)(coordsTo[3] * interp), 
275                                (float)(coordsTo[4] * interp), 
276                                (float)(coordsTo[5] * interp)
277                                );
278                        break;
279                    case PathIterator.SEG_LINETO:
280                        midPath.lineTo(
281                                (float)(coordsTo[0] * interp), 
282                                (float)(coordsTo[1] * interp)
283                                );
284                        break;
285                    case PathIterator.SEG_MOVETO:
286                        midPath.moveTo(
287                                (float)(coordsTo[0] * interp), 
288                                (float)(coordsTo[1] * interp)
289                                );
290                        break;
291                    case PathIterator.SEG_QUADTO:
292                        midPath.quadTo(
293                                (float)(coordsTo[0] * interp), 
294                                (float)(coordsTo[1] * interp), 
295                                (float)(coordsTo[2] * interp), 
296                                (float)(coordsTo[3] * interp)
297                                );
298                        break;
299                }
300            }
301            
302            return midPath;
303        }
304        else if (toPath != null)
305        {
306            PathIterator itFrom = fromPath.getPathIterator(new AffineTransform());
307            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
308            
309            GeneralPath midPath = new GeneralPath();
310            float[] coordsFrom = new float[6];
311            float[] coordsTo = new float[6];
312            
313            for (; !itFrom.isDone(); itFrom.next(), itTo.next())
314            {
315                int segFrom = itFrom.currentSegment(coordsFrom);
316                int segTo = itTo.currentSegment(coordsTo);
317                
318                if (segFrom != segTo)
319                {
320                    throw new RuntimeException("Path shape mismatch");
321                }
322                
323                switch (segFrom)
324                {
325                    case PathIterator.SEG_CLOSE:
326                        midPath.closePath();
327                        break;
328                    case PathIterator.SEG_CUBICTO:
329                        midPath.curveTo(
330                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
331                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
332                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
333                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp), 
334                                (float)(coordsFrom[4] * (1 - interp) + coordsTo[4] * interp), 
335                                (float)(coordsFrom[5] * (1 - interp) + coordsTo[5] * interp)
336                                );
337                        break;
338                    case PathIterator.SEG_LINETO:
339                        midPath.lineTo(
340                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
341                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
342                                );
343                        break;
344                    case PathIterator.SEG_MOVETO:
345                        midPath.moveTo(
346                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
347                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
348                                );
349                        break;
350                    case PathIterator.SEG_QUADTO:
351                        midPath.quadTo(
352                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
353                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
354                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
355                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp)
356                                );
357                        break;
358                }
359            }
360            
361            return midPath;
362        }
363        
364        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
365    }
366    
367    /**
368     * If this element is being accumulated, detemine the delta to accumulate by
369     */
370    public double repeatSkipSize(int reps)
371    {
372        boolean fromExists = !Double.isNaN(fromValue);
373        boolean toExists = !Double.isNaN(toValue);
374        boolean byExists = !Double.isNaN(byValue);
375        
376        if (fromExists && toExists)
377        {
378            return (toValue - fromValue) * reps;
379        }
380        else if (fromExists && byExists)
381        {
382            return (fromValue + byValue) * reps;
383        }
384        else if (toExists && byExists)
385        {
386            return toValue * reps;
387        }
388        else if (byExists)
389        {
390            return byValue * reps;
391        }
392
393        //Should not reach this line
394        return 0;
395    }
396
397    @Override
398    protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
399    {
400        super.rebuild(animTimeParser);
401
402        StyleAttribute sty = new StyleAttribute();
403
404        if (getPres(sty.setName("from")))
405        {
406            String strn = sty.getStringValue();
407            if (XMLParseUtil.isDouble(strn))
408            {
409                fromValue = XMLParseUtil.parseDouble(strn);
410            }
411            else
412            {
413                fromColor = ColorTable.parseColor(strn);
414                if (fromColor == null)
415                {
416                    //Try path
417                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
418                    dataType = DT_PATH;
419                }
420                else dataType = DT_COLOR;
421            }
422        }
423
424        if (getPres(sty.setName("to")))
425        {
426            String strn = sty.getStringValue();
427            if (XMLParseUtil.isDouble(strn))
428            {
429                toValue = XMLParseUtil.parseDouble(strn);
430            }
431            else
432            {
433                toColor = ColorTable.parseColor(strn);
434                if (toColor == null)
435                {
436                    //Try path
437                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
438                    dataType = DT_PATH;
439                }
440                else dataType = DT_COLOR;
441            }
442        }
443
444        if (getPres(sty.setName("by")))
445        {
446            String strn = sty.getStringValue();
447            if (strn != null) byValue = XMLParseUtil.parseDouble(strn);
448        }
449
450        if (getPres(sty.setName("values")))
451        {
452            String strn = sty.getStringValue();
453            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn);
454        }
455    }
456
457    /**
458     * @return the fromValue
459     */
460    public double getFromValue()
461    {
462        return fromValue;
463    }
464
465    /**
466     * @param fromValue the fromValue to set
467     */
468    public void setFromValue(double fromValue)
469    {
470        this.fromValue = fromValue;
471    }
472
473    /**
474     * @return the toValue
475     */
476    public double getToValue()
477    {
478        return toValue;
479    }
480
481    /**
482     * @param toValue the toValue to set
483     */
484    public void setToValue(double toValue)
485    {
486        this.toValue = toValue;
487    }
488
489    /**
490     * @return the byValue
491     */
492    public double getByValue()
493    {
494        return byValue;
495    }
496
497    /**
498     * @param byValue the byValue to set
499     */
500    public void setByValue(double byValue)
501    {
502        this.byValue = byValue;
503    }
504
505    /**
506     * @return the valuesValue
507     */
508    public double[] getValuesValue()
509    {
510        return valuesValue;
511    }
512
513    /**
514     * @param valuesValue the valuesValue to set
515     */
516    public void setValuesValue(double[] valuesValue)
517    {
518        this.valuesValue = valuesValue;
519    }
520
521    /**
522     * @return the fromColor
523     */
524    public Color getFromColor()
525    {
526        return fromColor;
527    }
528
529    /**
530     * @param fromColor the fromColor to set
531     */
532    public void setFromColor(Color fromColor)
533    {
534        this.fromColor = fromColor;
535    }
536
537    /**
538     * @return the toColor
539     */
540    public Color getToColor()
541    {
542        return toColor;
543    }
544
545    /**
546     * @param toColor the toColor to set
547     */
548    public void setToColor(Color toColor)
549    {
550        this.toColor = toColor;
551    }
552
553    /**
554     * @return the fromPath
555     */
556    public GeneralPath getFromPath()
557    {
558        return fromPath;
559    }
560
561    /**
562     * @param fromPath the fromPath to set
563     */
564    public void setFromPath(GeneralPath fromPath)
565    {
566        this.fromPath = fromPath;
567    }
568
569    /**
570     * @return the toPath
571     */
572    public GeneralPath getToPath()
573    {
574        return toPath;
575    }
576
577    /**
578     * @param toPath the toPath to set
579     */
580    public void setToPath(GeneralPath toPath)
581    {
582        this.toPath = toPath;
583    }
584    
585}