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}