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.effect;
027
028import javafx.beans.property.DoubleProperty;
029import javafx.beans.property.DoublePropertyBase;
030import javafx.beans.property.ObjectProperty;
031import javafx.beans.property.ObjectPropertyBase;
032import javafx.scene.Node;
033import javafx.scene.paint.Color;
034
035import com.sun.javafx.Utils;
036import com.sun.javafx.effect.EffectDirtyBits;
037import com.sun.javafx.effect.EffectUtils;
038import com.sun.javafx.geom.BaseBounds;
039import com.sun.javafx.geom.transform.BaseTransform;
040import com.sun.javafx.scene.BoundsAccessor;
041import com.sun.javafx.tk.Toolkit;
042
043
044/**
045 * An effect which creates a monochrome duplicate of an input with
046 * blurry edges.
047 * This effect is primarily used along with its default black color for
048 * purposes of combining it with the original to create a shadow.
049 * It can also be used with a light color and combined with an original
050 * to create a glow effect.
051 * The {@link DropShadow} effect is a utility effect which automatically
052 * combines this {@code Shadow} effect with an original graphic for ease
053 * of adding a shadow to an existing scene graph {@code Node} with a
054 * single effect.
055 */
056public class Shadow extends Effect {
057    private boolean changeIsLocal;
058
059    /**
060     * Creates a new instance of Shadow with default parameters.
061     */
062    public Shadow() {}
063
064    /**
065     * Creates a new instance of Shadow with specified radius and color.
066     * @param radius the radius of the shadow blur kernel
067     * @param color the shadow {@code Color}
068     */
069    public Shadow(double radius, Color color) {
070        setRadius(radius);
071        setColor(color);
072    }
073
074    /**
075     * Creates a new instance of Shadow with the specified blurType, color,
076     * radius.
077     * @param blurType the algorithm used to blur the shadow
078     * @param color the shadow {@code Color}
079     * @param radius the radius of the shadow blur kernel
080     */
081    public Shadow(BlurType blurType, Color color, double radius) {
082        setBlurType(blurType);
083        setColor(color);
084        setRadius(radius);
085    }
086
087    @Override
088    com.sun.scenario.effect.GeneralShadow impl_createImpl() {
089        return new com.sun.scenario.effect.GeneralShadow();
090    };
091    /**
092     * The input for this {@code Effect}.
093     * If set to {@code null}, or left unspecified, a graphical image of
094     * the {@code Node} to which the {@code Effect} is attached will be
095     * used as the input.
096     * @defaultValue null
097     */
098    private ObjectProperty<Effect> input;
099
100
101    public final void setInput(Effect value) {
102        inputProperty().set(value);
103    }
104
105    public final Effect getInput() {
106        return input == null ? null : input.get();
107    }
108
109    public final ObjectProperty<Effect> inputProperty() {
110        if (input == null) {
111            input = new EffectInputProperty("input");
112        }
113        return input;
114    }
115
116    @Override
117    boolean impl_checkChainContains(Effect e) {
118        Effect localInput = getInput();
119        if (localInput == null)
120            return false;
121        if (localInput == e)
122            return true;
123        return localInput.impl_checkChainContains(e);
124    }
125
126    /**
127     * The radius of the shadow blur kernel.
128     * This attribute controls the distance that the shadow is spread
129     * to each side of the source pixels.
130     * Setting the radius is equivalent to setting both the {@code width}
131     * and {@code height} attributes to a value of {@code (2 * radius + 1)}.
132     * <pre>
133     *       Min:   0.0
134     *       Max: 127.0
135     *   Default:  10.0
136     *  Identity:   0.0
137     * </pre>
138     * @defaultValue 10.0
139     */
140    private DoubleProperty radius;
141
142
143    public final void setRadius(double value) {
144        radiusProperty().set(value);
145    }
146
147    public final double getRadius() {
148        return radius == null ? 10 : radius.get();
149    }
150
151    public final DoubleProperty radiusProperty() {
152        if (radius == null) {
153            radius = new DoublePropertyBase(10) {
154
155                @Override
156                public void invalidated() {
157                    // gettter here is necessary to make the property valid
158                    double localRadius = getRadius();
159                    if (!changeIsLocal) {
160                        changeIsLocal = true;
161                        updateRadius(localRadius);
162                        changeIsLocal = false;
163                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
164                        effectBoundsChanged();
165                    }
166                }
167
168                @Override
169                public Object getBean() {
170                    return Shadow.this;
171                }
172
173                @Override
174                public String getName() {
175                    return "radius";
176                }
177            };
178        }
179        return radius;
180    }
181
182    private void updateRadius(double value) {
183        double newdim = (value * 2 + 1);
184        if (width != null && width.isBound()) {
185            if (height == null || !height.isBound()) {
186                setHeight(newdim * 2 - getWidth());
187            }
188        } else if (height != null && height.isBound()) {
189            setWidth(newdim * 2 - getHeight());
190        } else {
191            setWidth(newdim);
192            setHeight(newdim);
193        }
194    }
195
196    /**
197     * The horizontal size of the shadow blur kernel.
198     * This attribute controls the horizontal size of the total area over
199     * which the shadow of a single pixel is distributed by the blur algorithm.
200     * Values less than {@code 1.0} are not distributed beyond the original
201     * pixel and so have no blurring effect on the shadow.
202     * <pre>
203     *       Min:   0.0
204     *       Max: 255.0
205     *   Default:  21.0
206     *  Identity:  &lt;1.0
207     * </pre>
208     * @defaultValue 21.0
209     */
210    private DoubleProperty width;
211
212
213    public final void setWidth(double value) {
214        widthProperty().set(value);
215    }
216
217    public final double getWidth() {
218        return width == null ? 21 : width.get();
219    }
220
221    public final DoubleProperty widthProperty() {
222        if (width == null) {
223            width = new DoublePropertyBase(21) {
224
225                @Override
226                public void invalidated() {
227                    // gettter here is necessary to make the property valid
228                    double localWidth = getWidth();
229                    if (!changeIsLocal) {
230                        changeIsLocal = true;
231                        updateWidth(localWidth);
232                        changeIsLocal = false;
233                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
234                        effectBoundsChanged();
235                    }
236                }
237
238                @Override
239                public Object getBean() {
240                    return Shadow.this;
241                }
242
243                @Override
244                public String getName() {
245                    return "width";
246                }
247            };
248        }
249        return width;
250    }
251
252    private void updateWidth(double value) {
253        if (radius == null || !radius.isBound()) {
254            double newrad = ((value + getHeight()) / 2);
255            newrad = ((newrad - 1) / 2);
256            if (newrad < 0) {
257                newrad = 0;
258            }
259            setRadius(newrad);
260        } else {
261            // special case when radius is bound
262            if (height == null || !height.isBound()) {
263                double newdim = (getRadius() * 2 + 1);
264                setHeight(newdim * 2 - value);
265            }
266        }
267    }
268
269    /**
270     * The vertical size of the shadow blur kernel.
271     * This attribute controls the vertical size of the total area over
272     * which the shadow of a single pixel is distributed by the blur algorithm.
273     * Values less than {@code 1.0} are not distributed beyond the original
274     * pixel and so have no blurring effect on the shadow.
275     * <pre>
276     *       Min:   0.0
277     *       Max: 255.0
278     *   Default:  21.0
279     *  Identity:  &lt;1.0
280     * </pre>
281     * @defaultValue 21.0
282     */
283    private DoubleProperty height;
284
285
286    public final void setHeight(double value) {
287        heightProperty().set(value);
288    }
289
290    public final double getHeight() {
291        return height == null ? 21 : height.get();
292    }
293
294    public final DoubleProperty heightProperty() {
295        if (height == null) {
296            height = new DoublePropertyBase(21) {
297
298                @Override
299                public void invalidated() {
300                    // gettter here is necessary to make the property valid
301                    double localHeight = getHeight();
302                    if (!changeIsLocal) {
303                        changeIsLocal = true;
304                        updateHeight(localHeight);
305                        changeIsLocal = false;
306                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
307                        effectBoundsChanged();
308                    }
309                }
310
311                @Override
312                public Object getBean() {
313                    return Shadow.this;
314                }
315
316                @Override
317                public String getName() {
318                    return "height";
319                }
320            };
321        }
322        return height;
323    }
324
325    private void updateHeight(double value) {
326        if (radius == null || !radius.isBound()) {
327            double newrad = ((getWidth() + value) / 2);
328            newrad = ((newrad - 1) / 2);
329            if (newrad < 0) {
330                newrad = 0;
331            }
332            setRadius(newrad);
333        } else {
334            if (width == null || !width.isBound()) {
335                double newdim = (getRadius() * 2 + 1);
336                setWidth(newdim * 2 - value);
337            }
338        }
339    }
340
341    /**
342     * The algorithm used to blur the shadow.
343     * <pre>
344     *       Min: n/a
345     *       Max: n/a
346     *   Default: BlurType.THREE_PASS_BOX
347     *  Identity: n/a
348     * </pre>
349     * @defaultValue THREE_PASS_BOX
350     */
351    private ObjectProperty<BlurType> blurType;
352
353
354    public final void setBlurType(BlurType value) {
355        blurTypeProperty().set(value);
356    }
357
358    public final BlurType getBlurType() {
359        return blurType == null ? BlurType.THREE_PASS_BOX : blurType.get();
360    }
361
362    public final ObjectProperty<BlurType> blurTypeProperty() {
363        if (blurType == null) {
364            blurType = new ObjectPropertyBase<BlurType>(BlurType.THREE_PASS_BOX) {
365
366                @Override
367                public void invalidated() {
368                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
369                    effectBoundsChanged();
370                }
371
372                @Override
373                public Object getBean() {
374                    return Shadow.this;
375                }
376
377                @Override
378                public String getName() {
379                    return "blurType";
380                }
381            };
382        }
383        return blurType;
384    }
385
386    /**
387     * The shadow {@code Color}.
388     * <pre>
389     *       Min: n/a
390     *       Max: n/a
391     *   Default: Color.BLACK
392     *  Identity: n/a
393     * </pre>
394     * @defaultValue BLACK
395     */
396    private ObjectProperty<Color> color;
397
398
399    public final void setColor(Color value) {
400        colorProperty().set(value);
401    }
402
403    public final Color getColor() {
404        return color == null ? Color.BLACK : color.get();
405    }
406
407    public final ObjectProperty<Color> colorProperty() {
408        if (color == null) {
409            color = new ObjectPropertyBase<Color>(Color.BLACK) {
410
411                @Override
412                public void invalidated() {
413                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
414                }
415
416                @Override
417                public Object getBean() {
418                    return Shadow.this;
419                }
420
421                @Override
422                public String getName() {
423                    return "color";
424                }
425            };
426        }
427        return color;
428    }
429
430    private float getClampedWidth() {
431        return (float) Utils.clamp(0, getWidth(), 255);
432    }
433
434    private float getClampedHeight() {
435        return (float) Utils.clamp(0, getHeight(), 255);
436    }
437
438    private Color getColorInternal() {
439        Color c = getColor();
440        return c == null ? Color.BLACK : c;
441    }
442
443    private BlurType getBlurTypeInternal() {
444        BlurType bt = getBlurType();
445        return bt == null ? BlurType.THREE_PASS_BOX : bt;
446    }
447
448    @Override
449    void impl_update() {
450        Effect localInput = getInput();
451        if (localInput != null) {
452            localInput.impl_sync();
453        }
454
455        com.sun.scenario.effect.GeneralShadow peer =
456                (com.sun.scenario.effect.GeneralShadow) impl_getImpl();
457        peer.setInput(localInput == null ? null : localInput.impl_getImpl());
458        peer.setGaussianWidth(getClampedWidth());
459        peer.setGaussianHeight(getClampedHeight());
460        peer.setShadowMode(Toolkit.getToolkit().toShadowMode(getBlurTypeInternal()));
461        peer.setColor(Toolkit.getToolkit().toColor4f(getColorInternal()));
462    }
463
464    /**
465     * @treatAsPrivate implementation detail
466     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
467     */
468    @Deprecated
469    @Override
470    public BaseBounds impl_getBounds(BaseBounds bounds,
471                                     BaseTransform tx,
472                                     Node node,
473                                     BoundsAccessor boundsAccessor) {
474        bounds = EffectUtils.getInputBounds(bounds,
475                                            BaseTransform.IDENTITY_TRANSFORM,
476                                            node, boundsAccessor,
477                                            getInput());
478        return EffectUtils.getShadowBounds(bounds, tx,
479                                           getClampedWidth(),
480                                           getClampedHeight(),
481                                           getBlurTypeInternal());
482    }
483
484    /**
485     * 
486     * @treatAsPrivate implementation detail
487     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
488     */
489    @Deprecated
490    @Override
491    public Effect impl_copy() {
492        Shadow shadow = new Shadow(this.getBlurType(), this.getColor(), this.getRadius());
493        shadow.setInput(this.getInput());
494        shadow.setHeight(this.getHeight());
495        shadow.setWidth(this.getWidth());
496        return shadow;
497    }
498}