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 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 /** 455 * @return the fromValue 456 */ 457 public double getFromValue() 458 { 459 return fromValue; 460 } 461 462 /** 463 * @param fromValue the fromValue to set 464 */ 465 public void setFromValue(double fromValue) 466 { 467 this.fromValue = fromValue; 468 } 469 470 /** 471 * @return the toValue 472 */ 473 public double getToValue() 474 { 475 return toValue; 476 } 477 478 /** 479 * @param toValue the toValue to set 480 */ 481 public void setToValue(double toValue) 482 { 483 this.toValue = toValue; 484 } 485 486 /** 487 * @return the byValue 488 */ 489 public double getByValue() 490 { 491 return byValue; 492 } 493 494 /** 495 * @param byValue the byValue to set 496 */ 497 public void setByValue(double byValue) 498 { 499 this.byValue = byValue; 500 } 501 502 /** 503 * @return the valuesValue 504 */ 505 public double[] getValuesValue() 506 { 507 return valuesValue; 508 } 509 510 /** 511 * @param valuesValue the valuesValue to set 512 */ 513 public void setValuesValue(double[] valuesValue) 514 { 515 this.valuesValue = valuesValue; 516 } 517 518 /** 519 * @return the fromColor 520 */ 521 public Color getFromColor() 522 { 523 return fromColor; 524 } 525 526 /** 527 * @param fromColor the fromColor to set 528 */ 529 public void setFromColor(Color fromColor) 530 { 531 this.fromColor = fromColor; 532 } 533 534 /** 535 * @return the toColor 536 */ 537 public Color getToColor() 538 { 539 return toColor; 540 } 541 542 /** 543 * @param toColor the toColor to set 544 */ 545 public void setToColor(Color toColor) 546 { 547 this.toColor = toColor; 548 } 549 550 /** 551 * @return the fromPath 552 */ 553 public GeneralPath getFromPath() 554 { 555 return fromPath; 556 } 557 558 /** 559 * @param fromPath the fromPath to set 560 */ 561 public void setFromPath(GeneralPath fromPath) 562 { 563 this.fromPath = fromPath; 564 } 565 566 /** 567 * @return the toPath 568 */ 569 public GeneralPath getToPath() 570 { 571 return toPath; 572 } 573 574 /** 575 * @param toPath the toPath to set 576 */ 577 public void setToPath(GeneralPath toPath) 578 { 579 this.toPath = toPath; 580 } 581 582}