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