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}