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 January 26, 2004, 3:25 AM 035 */ 036package com.kitfox.svg; 037 038import com.kitfox.svg.xml.StyleAttribute; 039import java.awt.Color; 040import java.awt.geom.AffineTransform; 041import java.net.URI; 042import java.util.ArrayList; 043import java.util.Comparator; 044import java.util.Iterator; 045import java.util.logging.Level; 046import java.util.logging.Logger; 047 048/** 049 * @author Mark McKay 050 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 051 */ 052abstract public class Gradient extends FillElement 053{ 054 public static final String TAG_NAME = "gradient"; 055 056 public static final int SM_PAD = 0; 057 public static final int SM_REPEAT = 1; 058 public static final int SM_REFLECT = 2; 059 int spreadMethod = SM_PAD; 060 public static final int GU_OBJECT_BOUNDING_BOX = 0; 061 public static final int GU_USER_SPACE_ON_USE = 1; 062 protected int gradientUnits = GU_OBJECT_BOUNDING_BOX; 063 //Either this gradient contains a list of stops, or it will take it's 064 // stops from the referenced gradient 065 ArrayList<Stop> stops = new ArrayList<Stop>(); 066 URI stopRef = null; 067 protected AffineTransform gradientTransform = null; 068 069 //Cache arrays of stop values here 070 float[] stopFractions; 071 Color[] stopColors; 072 073 /** 074 * Creates a new instance of Gradient 075 */ 076 public Gradient() 077 { 078 } 079 080 @Override 081 public String getTagName() 082 { 083 return TAG_NAME; 084 } 085 086 /** 087 * Called after the start element but before the end element to indicate 088 * each child tag that has been processed 089 */ 090 @Override 091 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 092 { 093 super.loaderAddChild(helper, child); 094 095 if (!(child instanceof Stop)) 096 { 097 return; 098 } 099 appendStop((Stop) child); 100 } 101 102 @Override 103 protected void build() throws SVGException 104 { 105 super.build(); 106 107 StyleAttribute sty = new StyleAttribute(); 108 String strn; 109 110 if (getPres(sty.setName("spreadMethod"))) 111 { 112 strn = sty.getStringValue().toLowerCase(); 113 if (strn.equals("repeat")) 114 { 115 spreadMethod = SM_REPEAT; 116 } else if (strn.equals("reflect")) 117 { 118 spreadMethod = SM_REFLECT; 119 } else 120 { 121 spreadMethod = SM_PAD; 122 } 123 } 124 125 if (getPres(sty.setName("gradientUnits"))) 126 { 127 strn = sty.getStringValue().toLowerCase(); 128 if (strn.equals("userspaceonuse")) 129 { 130 gradientUnits = GU_USER_SPACE_ON_USE; 131 } else 132 { 133 gradientUnits = GU_OBJECT_BOUNDING_BOX; 134 } 135 } 136 137 if (getPres(sty.setName("gradientTransform"))) 138 { 139 gradientTransform = parseTransform(sty.getStringValue()); 140 } 141 //If we still don't have one, set it to identity 142 if (gradientTransform == null) 143 { 144 gradientTransform = new AffineTransform(); 145 } 146 147 148 //Check to see if we're using our own stops or referencing someone else's 149 if (getPres(sty.setName("xlink:href"))) 150 { 151 try 152 { 153 stopRef = sty.getURIValue(getXMLBase()); 154//System.err.println("Gradient: " + sty.getStringValue() + ", " + getXMLBase() + ", " + src); 155// URI src = getXMLBase().resolve(href); 156// stopRef = (Gradient)diagram.getUniverse().getElement(src); 157 } catch (Exception e) 158 { 159 throw new SVGException("Could not resolve relative URL in Gradient: " + sty.getStringValue() + ", " + getXMLBase(), e); 160 } 161 } 162 } 163 164 private void buildStops() 165 { 166 ArrayList<Stop> stopList = new ArrayList<Stop>(stops); 167 stopList.sort(new Comparator<Stop>(){ 168 public int compare(Stop o1, Stop o2) 169 { 170 return Float.compare(o1.offset, o2.offset); 171 } 172 }); 173 174 //Remove doubles 175 for (int i = stopList.size() - 2; i > 0; --i) 176 { 177 if (stopList.get(i + 1).offset == stopList.get(i).offset) 178 { 179 stopList.remove(i + 1); 180 } 181 } 182 183 184 stopFractions = new float[stopList.size()]; 185 stopColors = new Color[stopList.size()]; 186 int idx = 0; 187 for (Stop stop : stopList) 188 { 189 int stopColorVal = stop.color.getRGB(); 190 Color stopColor = new Color((stopColorVal >> 16) & 0xff, (stopColorVal >> 8) & 0xff, stopColorVal & 0xff, clamp((int) (stop.opacity * 255), 0, 255)); 191 192 stopColors[idx] = stopColor; 193 stopFractions[idx] = stop.offset; 194 idx++; 195 } 196 197 } 198 199 public float[] getStopFractions() 200 { 201 if (stopRef != null) 202 { 203 Gradient grad = (Gradient) diagram.getUniverse().getElement(stopRef); 204 return grad.getStopFractions(); 205 } 206 207 if (stopFractions != null) 208 { 209 return stopFractions; 210 } 211 212 buildStops(); 213 214 return stopFractions; 215 } 216 217 public Color[] getStopColors() 218 { 219 if (stopRef != null) 220 { 221 Gradient grad = (Gradient) diagram.getUniverse().getElement(stopRef); 222 return grad.getStopColors(); 223 } 224 225 if (stopColors != null) 226 { 227 return stopColors; 228 } 229 230 buildStops(); 231 232 return stopColors; 233 } 234 235// public void setStops(Color[] colors, float[] fractions) 236// { 237// if (colors.length != fractions.length) 238// { 239// throw new IllegalArgumentException(); 240// } 241// 242// this.stopColors = colors; 243// this.stopFractions = fractions; 244// stopRef = null; 245// } 246 247 private int clamp(int val, int min, int max) 248 { 249 if (val < min) 250 { 251 return min; 252 } 253 if (val > max) 254 { 255 return max; 256 } 257 return val; 258 } 259 260 public void setStopRef(URI grad) 261 { 262 stopRef = grad; 263 } 264 265 public void appendStop(Stop stop) 266 { 267 stops.add(stop); 268 } 269 270 /** 271 * Updates all attributes in this diagram associated with a time event. Ie, 272 * all attributes with track information. 273 * 274 * @return - true if this node has changed state as a result of the time 275 * update 276 */ 277 @Override 278 public boolean updateTime(double curTime) throws SVGException 279 { 280// if (trackManager.getNumTracks() == 0) return false; 281 boolean stateChange = false; 282 283 //Get current values for parameters 284 StyleAttribute sty = new StyleAttribute(); 285 String strn; 286 287 288 if (getPres(sty.setName("spreadMethod"))) 289 { 290 int newVal; 291 strn = sty.getStringValue().toLowerCase(); 292 if (strn.equals("repeat")) 293 { 294 newVal = SM_REPEAT; 295 } else if (strn.equals("reflect")) 296 { 297 newVal = SM_REFLECT; 298 } else 299 { 300 newVal = SM_PAD; 301 } 302 if (spreadMethod != newVal) 303 { 304 spreadMethod = newVal; 305 stateChange = true; 306 } 307 } 308 309 if (getPres(sty.setName("gradientUnits"))) 310 { 311 int newVal; 312 strn = sty.getStringValue().toLowerCase(); 313 if (strn.equals("userspaceonuse")) 314 { 315 newVal = GU_USER_SPACE_ON_USE; 316 } else 317 { 318 newVal = GU_OBJECT_BOUNDING_BOX; 319 } 320 if (newVal != gradientUnits) 321 { 322 gradientUnits = newVal; 323 stateChange = true; 324 } 325 } 326 327 if (getPres(sty.setName("gradientTransform"))) 328 { 329 AffineTransform newVal = parseTransform(sty.getStringValue()); 330 if (newVal != null && newVal.equals(gradientTransform)) 331 { 332 gradientTransform = newVal; 333 stateChange = true; 334 } 335 } 336 337 338 //Check to see if we're using our own stops or referencing someone else's 339 if (getPres(sty.setName("xlink:href"))) 340 { 341 try 342 { 343 URI newVal = sty.getURIValue(getXMLBase()); 344 if ((newVal == null && stopRef != null) || !newVal.equals(stopRef)) 345 { 346 stopRef = newVal; 347 stateChange = true; 348 } 349 } catch (Exception e) 350 { 351 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 352 "Could not parse xlink:href", e); 353 } 354 } 355 356 //Check stops, if any 357 for (Stop stop : stops) { 358 if (stop.updateTime(curTime)) 359 { 360 stateChange = true; 361 stopFractions = null; 362 stopColors = null; 363 } 364 } 365 366 return stateChange; 367 } 368}