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    protected double fromValue = Double.NaN;
075    protected double toValue = Double.NaN;
076    protected double byValue = Double.NaN;
077    protected double[] valuesValue;
078    
079    protected Color fromColor = null;
080    protected Color toColor = null;
081
082    protected GeneralPath fromPath = null;
083    protected GeneralPath toPath = null;
084
085    /** Creates a new instance of Animate */
086    public Animate()
087    {
088    }
089
090    public String getTagName()
091    {
092        return TAG_NAME;
093    }
094
095    public int getDataType()
096    {
097        return dataType;
098    }
099    
100    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
101    {
102                //Load style string
103        super.loaderStartElement(helper, attrs, parent);
104
105        String strn = attrs.getValue("from");
106        if (strn != null)
107        {
108            if (XMLParseUtil.isDouble(strn))
109            {
110                fromValue = XMLParseUtil.parseDouble(strn); 
111            } 
112//            else if (attrs.getValue("attributeName").equals("d"))
113//            {
114//                fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
115//                dataType = DT_PATH;
116//            }
117            else
118            {
119                fromColor = ColorTable.parseColor(strn); 
120                if (fromColor == null)
121                {
122                    //Try path
123                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
124                    dataType = DT_PATH;
125                }
126                else dataType = DT_COLOR;
127            }
128        }
129
130        strn = attrs.getValue("to");
131        if (strn != null)
132        {
133            if (XMLParseUtil.isDouble(strn))
134            {
135                toValue = XMLParseUtil.parseDouble(strn); 
136            } 
137            else
138            {
139                toColor = ColorTable.parseColor(strn); 
140                if (toColor == null)
141                {
142                    //Try path
143                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
144                    dataType = DT_PATH;
145                }
146                else dataType = DT_COLOR;
147            }
148        }
149
150        strn = attrs.getValue("by");
151        try 
152        {
153            if (strn != null) byValue = XMLParseUtil.parseDouble(strn); 
154        } catch (Exception e) {}
155
156        strn = attrs.getValue("values");
157        try 
158        {
159            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn); 
160        } catch (Exception e) {}
161    }
162    
163    /**
164     * Evaluates this animation element for the passed interpolation time.  Interp
165     * must be on [0..1].
166     */
167    public double eval(double interp)
168    {
169        boolean fromExists = !Double.isNaN(fromValue);
170        boolean toExists = !Double.isNaN(toValue);
171        boolean byExists = !Double.isNaN(byValue);
172        boolean valuesExists = valuesValue != null;
173        
174        if (valuesExists)
175        {
176            double sp = interp * valuesValue.length;
177            int ip = (int)sp;
178            double fp = sp - ip;
179            
180            int i0 = ip;
181            int i1 = ip + 1;
182            
183            if (i0 < 0) return valuesValue[0];
184            if (i1 >= valuesValue.length) return valuesValue[valuesValue.length - 1];
185            return valuesValue[i0] * (1 - fp) + valuesValue[i1] * fp;
186        }
187        else if (fromExists && toExists)
188        {
189            return toValue * interp + fromValue * (1.0 - interp);
190        }
191        else if (fromExists && byExists)
192        {
193            return fromValue + byValue * interp;
194        }
195        else if (toExists && byExists)
196        {
197            return toValue - byValue * (1.0 - interp);
198        }
199        else if (byExists)
200        {
201            return byValue * interp;
202        }
203        else if (toExists)
204        {
205            StyleAttribute style = new StyleAttribute(getAttribName());
206            try
207            {
208                getParent().getStyle(style, true, false);
209            }
210            catch (SVGException ex)
211            {
212                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 
213                    "Could not get from value", ex);
214            }
215            double from = style.getDoubleValue();
216            return toValue * interp + from * (1.0 - interp);
217        }
218  
219        //Should not reach this line
220        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
221    }
222
223    public Color evalColor(double interp)
224    {
225        if (fromColor == null && toColor != null)
226        {
227            float[] toCol = new float[3];
228            toColor.getColorComponents(toCol);
229            return new Color(toCol[0] * (float)interp, 
230                toCol[1] * (float)interp, 
231                toCol[2] * (float)interp);
232        }
233        else if (fromColor != null && toColor != null)
234        {
235            float nInterp = 1 - (float)interp;
236            
237            float[] fromCol = new float[3];
238            float[] toCol = new float[3];
239            fromColor.getColorComponents(fromCol);
240            toColor.getColorComponents(toCol);
241            return new Color(fromCol[0] * nInterp + toCol[0] * (float)interp, 
242                fromCol[1] * nInterp + toCol[1] * (float)interp, 
243                fromCol[2] * nInterp + toCol[2] * (float)interp);
244        }
245        
246        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
247    }
248
249    public GeneralPath evalPath(double interp)
250    {
251        if (fromPath == null && toPath != null)
252        {
253            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
254            
255            GeneralPath midPath = new GeneralPath();
256            float[] coordsTo = new float[6];
257            
258            for (; !itTo.isDone(); itTo.next())
259            {
260                int segTo = itTo.currentSegment(coordsTo);
261                
262                switch (segTo)
263                {
264                    case PathIterator.SEG_CLOSE:
265                        midPath.closePath();
266                        break;
267                    case PathIterator.SEG_CUBICTO:
268                        midPath.curveTo(
269                                (float)(coordsTo[0] * interp), 
270                                (float)(coordsTo[1] * interp), 
271                                (float)(coordsTo[2] * interp), 
272                                (float)(coordsTo[3] * interp), 
273                                (float)(coordsTo[4] * interp), 
274                                (float)(coordsTo[5] * interp)
275                                );
276                        break;
277                    case PathIterator.SEG_LINETO:
278                        midPath.lineTo(
279                                (float)(coordsTo[0] * interp), 
280                                (float)(coordsTo[1] * interp)
281                                );
282                        break;
283                    case PathIterator.SEG_MOVETO:
284                        midPath.moveTo(
285                                (float)(coordsTo[0] * interp), 
286                                (float)(coordsTo[1] * interp)
287                                );
288                        break;
289                    case PathIterator.SEG_QUADTO:
290                        midPath.quadTo(
291                                (float)(coordsTo[0] * interp), 
292                                (float)(coordsTo[1] * interp), 
293                                (float)(coordsTo[2] * interp), 
294                                (float)(coordsTo[3] * interp)
295                                );
296                        break;
297                }
298            }
299            
300            return midPath;
301        }
302        else if (toPath != null)
303        {
304            PathIterator itFrom = fromPath.getPathIterator(new AffineTransform());
305            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
306            
307            GeneralPath midPath = new GeneralPath();
308            float[] coordsFrom = new float[6];
309            float[] coordsTo = new float[6];
310            
311            for (; !itFrom.isDone(); itFrom.next(), itTo.next())
312            {
313                int segFrom = itFrom.currentSegment(coordsFrom);
314                int segTo = itTo.currentSegment(coordsTo);
315                
316                if (segFrom != segTo)
317                {
318                    throw new RuntimeException("Path shape mismatch");
319                }
320                
321                switch (segFrom)
322                {
323                    case PathIterator.SEG_CLOSE:
324                        midPath.closePath();
325                        break;
326                    case PathIterator.SEG_CUBICTO:
327                        midPath.curveTo(
328                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
329                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
330                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
331                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp), 
332                                (float)(coordsFrom[4] * (1 - interp) + coordsTo[4] * interp), 
333                                (float)(coordsFrom[5] * (1 - interp) + coordsTo[5] * interp)
334                                );
335                        break;
336                    case PathIterator.SEG_LINETO:
337                        midPath.lineTo(
338                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
339                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
340                                );
341                        break;
342                    case PathIterator.SEG_MOVETO:
343                        midPath.moveTo(
344                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
345                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
346                                );
347                        break;
348                    case PathIterator.SEG_QUADTO:
349                        midPath.quadTo(
350                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
351                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
352                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
353                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp)
354                                );
355                        break;
356                }
357            }
358            
359            return midPath;
360        }
361        
362        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
363    }
364    
365    /**
366     * If this element is being accumulated, detemine the delta to accumulate by
367     */
368    public double repeatSkipSize(int reps)
369    {
370        boolean fromExists = !Double.isNaN(fromValue);
371        boolean toExists = !Double.isNaN(toValue);
372        boolean byExists = !Double.isNaN(byValue);
373        
374        if (fromExists && toExists)
375        {
376            return (toValue - fromValue) * reps;
377        }
378        else if (fromExists && byExists)
379        {
380            return (fromValue + byValue) * reps;
381        }
382        else if (toExists && byExists)
383        {
384            return toValue * reps;
385        }
386        else if (byExists)
387        {
388            return byValue * reps;
389        }
390
391        //Should not reach this line
392        return 0;
393    }
394
395    protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
396    {
397        super.rebuild(animTimeParser);
398
399        StyleAttribute sty = new StyleAttribute();
400
401        if (getPres(sty.setName("from")))
402        {
403            String strn = sty.getStringValue();
404            if (XMLParseUtil.isDouble(strn))
405            {
406                fromValue = XMLParseUtil.parseDouble(strn);
407            }
408            else
409            {
410                fromColor = ColorTable.parseColor(strn);
411                if (fromColor == null)
412                {
413                    //Try path
414                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
415                    dataType = DT_PATH;
416                }
417                else dataType = DT_COLOR;
418            }
419        }
420
421        if (getPres(sty.setName("to")))
422        {
423            String strn = sty.getStringValue();
424            if (XMLParseUtil.isDouble(strn))
425            {
426                toValue = XMLParseUtil.parseDouble(strn);
427            }
428            else
429            {
430                toColor = ColorTable.parseColor(strn);
431                if (toColor == null)
432                {
433                    //Try path
434                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
435                    dataType = DT_PATH;
436                }
437                else dataType = DT_COLOR;
438            }
439        }
440
441        if (getPres(sty.setName("by")))
442        {
443            String strn = sty.getStringValue();
444            if (strn != null) byValue = XMLParseUtil.parseDouble(strn);
445        }
446
447        if (getPres(sty.setName("values")))
448        {
449            String strn = sty.getStringValue();
450            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn);
451        }
452    }
453    
454}