Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.paint;
027
028import java.util.List;
029
030import com.sun.javafx.beans.annotations.Default;
031import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
032import com.sun.javafx.scene.paint.GradientUtils;
033import com.sun.javafx.tk.Toolkit;
034
035/**
036 * <p>The {@code LinearGradient} class fills a shape
037 * with a linear color gradient pattern. The user may specify two or
038 * more gradient colors, and this Paint will provide an interpolation
039 * between each color.</p>
040 *
041 * <p>
042 * The application provides an array of {@code Stop}s specifying how to distribute
043 * the colors along the gradient. The {@code Stop#offset} variable must be
044 * the range 0.0 to 1.0 and act like keyframes along the gradient.
045 * The offsets mark where the gradient should be exactly a particular color. </p>
046 *
047 * <p>If the proportional variable is set to true
048 * then the start and end points of the gradient
049 * should be specified relative to the unit square (0.0->1.0) and will
050 * be stretched across the shape. If the proportional variable is set
051 * to false, then the start and end points should be specified
052 * in the local coordinate system of the shape and the gradient will
053 * not be stretched at all.</p>
054 *
055 * <p>
056 * The two filled rectangles in the example below will render the same.
057 * The one on the left uses proportional coordinates to specify
058 * the end points of the gradient.  The one on the right uses absolute
059 * coordinates.  Both of them fill the specified rectangle with a
060 * horizontal gradient that varies from black to red</p>
061 *
062<PRE>
063// object bounding box relative (proportional = true)
064Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.RED)};
065LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops);
066Rectangle r1 = new Rectangle(0, 0, 100, 100);
067r1.setFill(lg1);
068
069// user space relative (proportional: = false)
070LinearGradient lg2 = new LinearGradient(125, 0, 225, 0, false, CycleMethod.NO_CYCLE, stops);
071Rectangle r2 = new Rectangle(125, 0, 100, 100);
072r2.setFill(lg2);
073</PRE>
074 */
075public final class LinearGradient extends Paint {
076    private double startX;
077
078    /**
079     * Defines the X coordinate of the gradient axis start point.
080     * If proportional is true (the default), this value specifies a
081     * point on a unit square that will be scaled to match the size of the
082     * the shape that the gradient fills.
083     (
084     * @defaultValue 0.0
085     */
086    public final double getStartX() {
087        return startX;
088    }
089
090    private double startY;
091
092    /**
093     * Defines the Y coordinate of the gradient axis start point.
094     * If proportional is true (the default), this value specifies a
095     * point on a unit square that will be scaled to match the size of the
096     * the shape that the gradient fills.
097     *
098     * @defaultValue 0.0
099     */
100    public final double getStartY() {
101        return startY;
102    }
103
104    private double endX;
105
106    /**
107     * Defines the X coordinate of the gradient axis end point.
108     * If proportional is true (the default), this value specifies a
109     * point on a unit square that will be scaled to match the size of the
110     * the shape that the gradient fills.
111     *
112     * @defaultValue 1.0
113     */
114    public final double getEndX() {
115        return endX;
116    }
117
118    private double endY;
119
120    /**
121     * Defines the Y coordinate of the gradient axis end point.
122     * If proportional is true (the default), this value specifies a
123     * point on a unit square that will be scaled to match the size of the
124     * the shape that the gradient fills.
125     *
126     * @defaultValue 1.0
127     */
128    public final double getEndY() {
129        return endY;
130    }
131
132    private boolean proportional;
133
134    /**
135     * Indicates whether start and end locations are proportional or absolute.
136     * If this flag is true, the two end points are defined in a coordinate
137     * space where coordinates in the range {@code [0..1]} are scaled to map
138     * onto the bounds of the shape that the gradient fills.
139     * If this flag is false, then the coordinates are specified in the local
140     * coordinate system of the node.
141     *
142     * @defaultValue true
143     */
144    public final boolean isProportional() {
145        return proportional;
146    }
147
148    private CycleMethod cycleMethod;
149
150    /**
151     * Defines which of the following cycle method is applied
152     * to the {@code LinearGradient}: {@code CycleMethod.NO_CYCLE},
153     * {@code CycleMethod.REFLECT}, or {@code CycleMethod.REPEAT}.
154     *
155     * @defaultValue NO_CYCLE
156     */
157    public final CycleMethod getCycleMethod() {
158        return cycleMethod;
159    }
160
161    private List<Stop> stops;
162
163    /**
164     * A sequence of 2 or more {@code Stop} values specifying how to distribute
165     * the colors along the gradient. These values must be in the range
166     * 0.0 to 1.0. They act like key frames along the gradient: they mark where
167     * the gradient should be exactly a particular color.
168     *
169     * <p>Each stop in the sequence must have an offset that is greater than the
170     * previous stop in the sequence.</p>
171     *
172     * <p>The list is unmodifiable and will throw
173     * {@code UnsupportedOperationException} on each modification attempt.</p>
174     *
175     * @defaultValue empty
176     */
177    @ReturnsUnmodifiableCollection
178    public final List<Stop> getStops() {
179        return stops;
180    }
181
182    /**
183     * @inheritDoc
184     */
185    @Override public final boolean isOpaque() {
186        return opaque;
187    }
188
189    private final boolean opaque;
190
191    /**
192     * A cached reference to the platform paint, no point recomputing twice
193     */
194    private Object platformPaint;
195
196    /**
197     * The cached hash code, used to improve performance in situations where
198     * we cache gradients, such as in the CSS routines.
199     */
200    private int hash;
201
202    /**
203     * Creates a new instance of LinearGradient.
204     * @param startX the X coordinate of the gradient axis start point
205     * @param startY the Y coordinate of the gradient axis start point
206     * @param endX the X coordinate of the gradient axis end point
207     * @param endY the Y coordinate of the gradient axis end point
208     * @param proportional whether the coordinates are proportional
209     * to the shape which this gradient fills
210     * @param cycleMethod cycle method applied to the gradient
211     * @param stops the gradient's color specification
212     */
213    public LinearGradient(
214            double startX,
215            double startY,
216            @Default("1") double endX,
217            @Default("1") double endY,
218            @Default("true") boolean proportional,
219            CycleMethod cycleMethod,
220            Stop... stops) {
221        this.startX = startX;
222        this.startY = startY;
223        this.endX = endX;
224        this.endY = endY;
225        this.proportional = proportional;
226        this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE: cycleMethod;
227        this.stops = Stop.normalize(stops);
228        this.opaque = determineOpacity();
229    }
230
231    /**
232     * Creates a new instance of LinearGradient.
233     * @param startX the X coordinate of the gradient axis start point
234     * @param startY the Y coordinate of the gradient axis start point
235     * @param endX the X coordinate of the gradient axis end point
236     * @param endY the Y coordinate of the gradient axis end point
237     * @param proportional whether the coordinates are proportional
238     * to the shape which this gradient fills
239     * @param cycleMethod cycle method applied to the gradient
240     * @param stops the gradient's color specification
241     */
242    public LinearGradient(
243            double startX,
244            double startY,
245            @Default("1") double endX,
246            @Default("1") double endY,
247            @Default("true") boolean proportional,
248            CycleMethod cycleMethod,
249            List<Stop> stops) {
250        this.startX = startX;
251        this.startY = startY;
252        this.endX = endX;
253        this.endY = endY;
254        this.proportional = proportional;
255        this.cycleMethod = (cycleMethod == null) ? CycleMethod.NO_CYCLE: cycleMethod;
256        this.stops = Stop.normalize(stops);
257        this.opaque = determineOpacity();
258    }
259
260    /**
261     * Iterate over all the stops. If any one of them has a transparent
262     * color, then we return false. If there are no stops, we return false.
263     * Otherwise, we return true. Note that this is called AFTER Stop.normalize,
264     * which ensures that we always have at least 2 stops.
265     *
266     * @return Whether this gradient is opaque
267     */
268    private boolean determineOpacity() {
269        final int numStops = this.stops.size();
270        for (int i = 0; i < numStops; i++) {
271            if (!stops.get(i).getColor().isOpaque()) {
272                return false;
273            }
274        }
275        return true;
276    }
277
278    @Override
279    Object acc_getPlatformPaint() {
280        if (platformPaint == null) {
281            platformPaint = Toolkit.getToolkit().getPaint(this);
282        }
283        return platformPaint;
284    }
285
286    /**
287     * Indicates whether some other object is "equal to" this one.
288     * @param obj the reference object with which to compare.
289     * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise.
290     */
291    @Override public boolean equals(Object obj) {
292        if (obj == null) return false;
293        if (obj == this) return true;
294        if (obj instanceof LinearGradient) {
295            final LinearGradient other = (LinearGradient) obj;
296            if ((startX != other.startX) ||
297                (startY != other.startY) ||
298                (endX != other.endX) ||
299                (endY != other.endY) ||
300                (proportional != other.proportional) ||
301                (cycleMethod != other.cycleMethod)) return false;
302            if (!stops.equals(other.stops)) return false;
303            return true;
304        } else return false;
305    }
306
307    /**
308     * Returns a hash code for this {@code LinearGradient} object.
309     * @return a hash code for this {@code LinearGradient} object.
310     */
311    @Override public int hashCode() {
312        if (hash == 0) {
313            long bits = 17L;
314            bits = 37L * bits + Double.doubleToLongBits(startX);
315            bits = 37L * bits + Double.doubleToLongBits(startY);
316            bits = 37L * bits + Double.doubleToLongBits(endX);
317            bits = 37L * bits + Double.doubleToLongBits(endY);
318            bits = 37L * bits + ((proportional) ? 1231L : 1237L);
319            bits = 37L * bits + cycleMethod.hashCode();
320            for (Stop stop: stops) {
321                bits = 37L * bits + stop.hashCode();
322            }
323            hash = (int) (bits ^ (bits >> 32));
324        }
325        return hash;
326    }
327
328    /**
329     * Returns a string representation of this {@code LinearGradient} object.
330     * @return a string representation of this {@code LinearGradient} object.
331     */
332    @Override public String toString() {
333        final StringBuilder s = new StringBuilder("linear-gradient(from ")
334                .append(GradientUtils.lengthToString(startX, proportional))
335                .append(" ").append(GradientUtils.lengthToString(startY, proportional))
336                .append(" to ").append(GradientUtils.lengthToString(endX, proportional))
337                .append(" ").append(GradientUtils.lengthToString(endY, proportional))
338                .append(", ");
339
340        switch (cycleMethod) {
341            case REFLECT:
342                s.append("reflect").append(", ");
343                break;
344            case REPEAT:
345                s.append("repeat").append(", ");
346                break;
347        }
348
349        for (Stop stop : stops) {
350            s.append(stop).append(", ");
351        }
352
353        s.delete(s.length() - 2, s.length());
354        s.append(")");
355
356        return s.toString();
357    }
358
359    /**
360     * Creates a linear gradient value from a string representation. 
361     * <p>The format of the string representation is based on 
362     * JavaFX CSS specification for linear gradient which is 
363     * <pre>
364     * linear-gradient( [ [from &lt;point&gt; to &lt;point&gt;| [ to &lt;side-or-corner&gt;], ]? [ [ repeat | reflect ], ]? &lt;color-stop&gt;[, &lt;color-stop&gt;]+)
365     * </pre>
366     * where
367     * <pre>
368     * &lt;side-or-corner&gt; = [left | right] || [top | bottom] 
369     * &lt;point&gt; = [ [ &lt;length&gt; &lt;length&gt; ] | [ &lt;percentage&gt; | &lt;percentage&gt; ] ]
370     * &lt;color-stop&gt; = [ &lt;color&gt; [ &lt;percentage&gt; | &lt;length&gt;]? ]
371     * </pre>
372     * </p>
373     * <p> Currently length can be only specified in px, the specification of unit can be omited.
374     * Format of color representation is the one used in {@link Color#web(String color)}.
375     * The linear-gradient keyword can be omited.
376     * For additional information about the format of string representation, see the 
377     * <a href="../doc-files/cssref.html">CSS Reference Guide</a>.
378     * </p>
379     * 
380     * Examples:
381     * <pre><code>
382     * LinearGradient g
383     *      = LinearGradient.valueOf("linear-gradient(from 0% 0% to 100% 100%, red  0% , blue 30%,  black 100%)");
384     * LinearGradient g
385     *      = LinearGradient.valueOf("from 0% 0% to 100% 100%, red  0% , blue 30%,  black 100%");
386     * LinearGradient g
387     *      = LinearGradient.valueOf("linear-gradient(from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%)");
388     * LinearGradient g
389     *      = LinearGradient.valueOf("from 0px 0px to 200px 0px, #00ff00 0%, 0xff0000 50%, 0x1122ff40 100%");
390     * </code></pre>
391     *
392     * @param value the string to convert
393     * @throws NullPointerException if the {@code value} is {@code null}
394     * @throws IllegalArgumentException if the {@code value} cannot be parsed
395     * @return a {@code LinearGradient} object holding the value represented 
396     * by the string argument.
397     */
398    public static LinearGradient valueOf(String value) {
399        if (value == null) {
400            throw new NullPointerException("gradient must be specified");
401        }
402
403        String start = "linear-gradient(";
404        String end = ")";
405        if (value.startsWith(start)) {
406            if (!value.endsWith(end)) {
407                throw new IllegalArgumentException("Invalid gradient specification, "
408                        + "must end with \"" + end + '"');
409            }
410            value = value.substring(start.length(), value.length() - end.length());
411        }       
412
413        GradientUtils.Parser parser = new GradientUtils.Parser(value);
414        if (parser.getSize() < 2) {
415            throw new IllegalArgumentException("Invalid gradient specification");
416        }
417
418        GradientUtils.Point startX = GradientUtils.Point.MIN;
419        GradientUtils.Point startY = GradientUtils.Point.MIN;
420        GradientUtils.Point endX = GradientUtils.Point.MIN;
421        GradientUtils.Point endY = GradientUtils.Point.MIN;
422
423        String[] tokens = parser.splitCurrentToken();
424        if ("from".equals(tokens[0])) {
425            GradientUtils.Parser.checkNumberOfArguments(tokens, 5);
426            startX = parser.parsePoint(tokens[1]);
427            startY = parser.parsePoint(tokens[2]);
428            if (!"to".equals(tokens[3])) {
429                throw new IllegalArgumentException("Invalid gradient specification, \"to\" expected");
430            }
431            endX = parser.parsePoint(tokens[4]);
432            endY = parser.parsePoint(tokens[5]);
433            parser.shift();
434        } else if ("to".equals(tokens[0])) {
435            int horizontalSet = 0;
436            int verticalSet = 0;
437
438            for (int i = 1; i < 3 && i < tokens.length; i++) {
439                if ("left".equals(tokens[i])) {
440                    startX = GradientUtils.Point.MAX;
441                    endX = GradientUtils.Point.MIN;
442                    horizontalSet++;
443                } else if ("right".equals(tokens[i])) {
444                    startX = GradientUtils.Point.MIN;
445                    endX = GradientUtils.Point.MAX;
446                    horizontalSet++;
447                } else if ("top".equals(tokens[i])) {
448                    startY = GradientUtils.Point.MAX;
449                    endY = GradientUtils.Point.MIN;
450                    verticalSet++;
451                } else if ("bottom".equals(tokens[i])) {
452                    startY = GradientUtils.Point.MIN;
453                    endY = GradientUtils.Point.MAX;
454                    verticalSet++;
455                } else {
456                    throw new IllegalArgumentException("Invalid gradient specification,"
457                        + " unknown value after 'to'");
458                }
459            }
460            if (verticalSet > 1) {
461                throw new IllegalArgumentException("Invalid gradient specification,"
462                        + " vertical direction set twice after 'to'");
463            }
464            if (horizontalSet > 1) {
465                throw new IllegalArgumentException("Invalid gradient specification,"
466                        + " horizontal direction set twice after 'to'");
467            }
468            parser.shift();
469        } else {
470            // default is "to bottom"
471            startY = GradientUtils.Point.MIN;
472            endY = GradientUtils.Point.MAX;
473        }
474
475        // repeat/reflect
476        CycleMethod method = CycleMethod.NO_CYCLE;
477        String currentToken = parser.getCurrentToken();
478        if ("repeat".equals(currentToken)) {
479            method = CycleMethod.REPEAT;
480            parser.shift();
481        } else if ("reflect".equals(currentToken)) {
482            method = CycleMethod.REFLECT;
483            parser.shift();
484        }
485
486        double dist = 0;
487        if (!startX.proportional) {
488            double dx = endX.value - startX.value;
489            double dy = endY.value - startY.value;
490            dist = Math.sqrt(dx*dx + dy*dy);
491        }
492
493        Stop[] stops = parser.parseStops(startX.proportional, dist);
494
495        return new LinearGradient(startX.value, startY.value, endX.value, endY.value, 
496                                  startX.proportional, method, stops);
497    }
498}