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 * A high-level effect that renders a shadow of the given content behind
046 * the content with the specified color, radius, and offset.
047 *
048 * <p>
049 * Example:
050 * <pre><code>
051
052 * DropShadow dropShadow = new DropShadow();
053 * dropShadow.setRadius(5.0);
054 * dropShadow.setOffsetX(3.0);
055 * dropShadow.setOffsetY(3.0);
056 * dropShadow.setColor(Color.color(0.4, 0.5, 0.5));  *
057 * 
058 * Text text = new Text();
059 * text.setEffect(dropShadow);
060 * text.setCache(true);
061 * text.setX(10.0);
062 * text.setY(70.0);
063 * text.setFill(Color.web("0x3b596d"));
064 * text.setText("JavaFX drop shadow...");
065 * text.setFont(Font.font(null, FontWeight.BOLD, 40));
066 *
067 * DropShadow dropShadow2 = new DropShadow();
068 * dropShadow2.setOffsetX(6.0);
069 * dropShadow2.setOffsetY(4.0);
070 *
071 * Circle circle = new Circle();
072 * circle.setEffect(dropShadow2);
073 * circle.setCenterX(50.0);
074 * circle.setCenterY(125.0);
075 * circle.setRadius(30.0);
076 * circle.setFill(Color.STEELBLUE);
077 * circle.setCache(true);
078 * </pre></code>
079 *
080 * <p>
081 * The code above produces the following:
082 * </p>
083 * <p>
084 * <img src="doc-files/dropshadow.png"/>
085 * </p>
086 */
087public class DropShadow extends Effect {
088    private boolean changeIsLocal;
089
090    /**
091     * Creates a new instance of DropShadow with default parameters.
092     */
093    public DropShadow() {}
094
095    /**
096     * Creates a new instance of DropShadow with specified radius and color.
097     * @param radius the radius of the shadow blur kernel
098     * @param color the shadow {@code Color}
099     */
100    public DropShadow(double radius, Color color) {
101        setRadius(radius);
102        setColor(color);
103    }
104
105    /**
106     * Creates a new instance of DropShadow with the specified radius, offsetX,
107     * offsetY and color.
108     * @param radius the radius of the shadow blur kernel
109     * @param offsetX the shadow offset in the x direction
110     * @param offsetY the shadow offset in the y direction
111     * @param color the shadow {@code Color}
112     */
113    public DropShadow(double radius, double offsetX, double offsetY, Color color) {
114        setRadius(radius);
115        setOffsetX(offsetX);
116        setOffsetY(offsetY);
117        setColor(color);
118    }
119    
120    /**
121     * Creates a new instance of DropShadow with the specified blurType, color,
122     * radius, spread, offsetX and offsetY.
123     * @param blurType the algorithm used to blur the shadow
124     * @param color the shadow {@code Color}
125     * @param radius the radius of the shadow blur kernel
126     * @param spread the portion of the radius where the contribution of
127     * the source material will be 100%
128     * @param offsetX the shadow offset in the x direction
129     * @param offsetY the shadow offset in the y direction
130     */
131    public DropShadow(BlurType blurType, Color color, double radius, double spread, 
132            double offsetX, double offsetY) {
133        setBlurType(blurType);
134        setColor(color);
135        setRadius(radius);
136        setSpread(spread);
137        setOffsetX(offsetX);
138        setOffsetY(offsetY);
139    }
140
141    @Override
142    com.sun.scenario.effect.DropShadow impl_createImpl() {
143        return new com.sun.scenario.effect.DropShadow();
144    };
145    /**
146     * The input for this {@code Effect}.
147     * If set to {@code null}, or left unspecified, a graphical image of
148     * the {@code Node} to which the {@code Effect} is attached will be
149     * used as the input.
150     * @defaultValue null
151     * @since JavaFX 1.3
152     */
153    private ObjectProperty<Effect> input;
154
155
156    public final void setInput(Effect value) {
157        inputProperty().set(value);
158    }
159
160    public final Effect getInput() {
161        return input == null ? null : input.get();
162    }
163
164    public final ObjectProperty<Effect> inputProperty() {
165        if (input == null) {
166            input = new EffectInputProperty("input");
167        }
168        return input;
169    }
170
171    @Override
172    boolean impl_checkChainContains(Effect e) {
173        Effect localInput = getInput();
174        if (localInput == null)
175            return false;
176        if (localInput == e)
177            return true;
178        return localInput.impl_checkChainContains(e);
179    }
180
181    /**
182     * The radius of the shadow blur kernel.
183     * This attribute controls the distance that the shadow is spread
184     * to each side of the source pixels.
185     * Setting the radius is equivalent to setting both the {@code width}
186     * and {@code height} attributes to a value of {@code (2 * radius + 1)}.
187     * <pre>
188     *       Min:   0.0
189     *       Max: 127.0
190     *   Default:  10.0
191     *  Identity:   0.0
192     * </pre>
193     * @defaultValue 10.0
194     */
195    private DoubleProperty radius;
196
197
198    public final void setRadius(double value) {
199        radiusProperty().set(value);
200    }
201
202    public final double getRadius() {
203        return radius == null ? 10 : radius.get();
204    }
205
206    public final DoubleProperty radiusProperty() {
207        if (radius == null) {
208            radius = new DoublePropertyBase(10) {
209
210                @Override
211                public void invalidated() {
212                    // gettter here is necessary to make the property valid
213                    double localRadius = getRadius();
214                    if (!changeIsLocal) {
215                        changeIsLocal = true;
216                        updateRadius(localRadius);
217                        changeIsLocal = false;
218                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
219                        effectBoundsChanged();
220                    }
221                }
222
223                @Override
224                public Object getBean() {
225                    return DropShadow.this;
226                }
227
228                @Override
229                public String getName() {
230                    return "radius";
231                }
232            };
233        }
234        return radius;
235    }
236
237    private void updateRadius(double value) {
238        double newdim = (value * 2 + 1);
239        if (width != null && width.isBound()) {
240            // if neither is readonly we would set both width and
241            // height to radius*2+1 (i.e. newdim), but if one of
242            // them is bound then we need to set the other to a
243            // value that would map back to the radius we have been
244            // given.
245            // To do that we equate the average of the two values
246            // to newdim, the value we want them both to have, and
247            // then solve for the missing value:
248            // avg(w,h) == radius * 2 + 1
249            // (w+h)/2 == newdim
250            // w+h == newdim * 2
251            // h = newdim * 2 - w
252            // w = newdim * 2 - h
253            if (height == null || !height.isBound()) {
254                setHeight(newdim * 2 - getWidth());
255            }
256        } else if (height != null && height.isBound()) {
257            setWidth(newdim * 2 - getHeight());
258        } else {
259            setWidth(newdim);
260            setHeight(newdim);
261        }
262    }
263
264    /**
265     * The horizontal size of the shadow blur kernel.
266     * This attribute controls the horizontal size of the total area over
267     * which the shadow of a single pixel is distributed by the blur algorithm.
268     * Values less than {@code 1.0} are not distributed beyond the original
269     * pixel and so have no blurring effect on the shadow.
270     * <pre>
271     *       Min:   0.0
272     *       Max: 255.0
273     *   Default:  21.0
274     *  Identity:  &lt;1.0
275     * </pre>
276     * @defaultValue 21.0
277     */
278    private DoubleProperty width;
279
280
281    public final void setWidth(double value) {
282        widthProperty().set(value);
283    }
284
285    public final double getWidth() {
286        return width == null ? 21 : width.get();
287    }
288
289    public final DoubleProperty widthProperty() {
290        if (width == null) {
291            width = new DoublePropertyBase(21) {
292
293                @Override
294                public void invalidated() {
295                    // gettter here is necessary to make the property valid
296                    double localWidth = getWidth();
297                    if (!changeIsLocal) {
298                        changeIsLocal = true;
299                        updateWidth(localWidth);
300                        changeIsLocal = false;
301                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
302                        effectBoundsChanged();
303                    }
304                }
305
306                @Override
307                public Object getBean() {
308                    return DropShadow.this;
309                }
310
311                @Override
312                public String getName() {
313                    return "width";
314                }
315            };
316        }
317        return width;
318    }
319
320    private void updateWidth(double value) {
321        if (radius == null || !radius.isBound()) {
322            double newrad = ((value + getHeight()) / 2);
323            newrad = ((newrad - 1) / 2);
324            if (newrad < 0) {
325                newrad = 0;
326            }
327            setRadius(newrad);
328        } else {
329            if (height == null || !height.isBound()) {
330                double newdim = (getRadius() * 2 + 1);
331                setHeight(newdim * 2 - value);
332            }
333        }
334    }
335
336    /**
337     * The vertical size of the shadow blur kernel.
338     * This attribute controls the vertical size of the total area over
339     * which the shadow of a single pixel is distributed by the blur algorithm.
340     * Values less than {@code 1.0} are not distributed beyond the original
341     * pixel and so have no blurring effect on the shadow.
342     * <pre>
343     *       Min:   0.0
344     *       Max: 255.0
345     *   Default:  21.0
346     *  Identity:  &lt;1.0
347     * </pre>
348     * @defaultValue 21.0
349     */
350    private DoubleProperty height;
351
352
353    public final void setHeight(double value) {
354        heightProperty().set(value);
355    }
356
357    public final double getHeight() {
358        return height == null ? 21 : height.get();
359    }
360
361    public final DoubleProperty heightProperty() {
362        if (height == null) {
363            height = new DoublePropertyBase(21) {
364
365                @Override
366                public void invalidated() {
367                    // gettter here is necessary to make the property valid
368                    double localHeight = getHeight();
369                    if (!changeIsLocal) {
370                        changeIsLocal = true;
371                        updateHeight(localHeight);
372                        changeIsLocal = false;
373                        markDirty(EffectDirtyBits.EFFECT_DIRTY);
374                        effectBoundsChanged();
375                    }
376                }
377
378                @Override
379                public Object getBean() {
380                    return DropShadow.this;
381                }
382
383                @Override
384                public String getName() {
385                    return "height";
386                }
387            };
388        }
389        return height;
390    }
391
392    private void updateHeight(double value) {
393        if (radius == null || !radius.isBound()) {
394            double newrad = ((getWidth() + value) / 2);
395            newrad = ((newrad - 1) / 2);
396            if (newrad < 0) {
397                newrad = 0;
398            }
399            setRadius(newrad);
400        } else {
401            if (width == null || !width.isBound()) {
402                double newdim = (getRadius() * 2 + 1);
403                setWidth(newdim * 2 - value);
404            }
405        }
406    }
407
408    /**
409     * The algorithm used to blur the shadow.
410     * <pre>
411     *       Min: n/a
412     *       Max: n/a
413     *   Default: BlurType.THREE_PASS_BOX
414     *  Identity: n/a
415     * </pre>
416     * @defaultValue THREE_PASS_BOX
417     */
418    private ObjectProperty<BlurType> blurType;
419
420
421    public final void setBlurType(BlurType value) {
422        blurTypeProperty().set(value);
423    }
424
425    public final BlurType getBlurType() {
426        return blurType == null ? BlurType.THREE_PASS_BOX : blurType.get();
427    }
428
429    public final ObjectProperty<BlurType> blurTypeProperty() {
430        if (blurType == null) {
431            blurType = new ObjectPropertyBase<BlurType>(BlurType.THREE_PASS_BOX) {
432
433                @Override
434                public void invalidated() {
435                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
436                    effectBoundsChanged();
437                }
438
439                @Override
440                public Object getBean() {
441                    return DropShadow.this;
442                }
443
444                @Override
445                public String getName() {
446                    return "blurType";
447                }
448            };
449        }
450        return blurType;
451    }
452
453    /**
454     * The spread of the shadow.
455     * The spread is the portion of the radius where the contribution of
456     * the source material will be 100%.
457     * The remaining portion of the radius will have a contribution
458     * controlled by the blur kernel.
459     * A spread of {@code 0.0} will result in a distribution of the
460     * shadow determined entirely by the blur algorithm.
461     * A spread of {@code 1.0} will result in a solid growth outward of the
462     * source material opacity to the limit of the radius with a very sharp
463     * cutoff to transparency at the radius.
464     * <pre>
465     *       Min: 0.0
466     *       Max: 1.0
467     *   Default: 0.0
468     *  Identity: 0.0
469     * </pre>
470     * @defaultValue 0.0
471     */
472    private DoubleProperty spread;
473
474
475    public final void setSpread(double value) {
476        spreadProperty().set(value);
477    }
478
479    public final double getSpread() {
480        return spread == null ? 0.0 : spread.get();
481    }
482
483    public final DoubleProperty spreadProperty() {
484        if (spread == null) {
485            spread = new DoublePropertyBase() {
486
487                @Override
488                public void invalidated() {
489                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
490                }
491
492                @Override
493                public Object getBean() {
494                    return DropShadow.this;
495                }
496
497                @Override
498                public String getName() {
499                    return "spread";
500                }
501            };
502        }
503        return spread;
504    }
505
506    /**
507     * The shadow {@code Color}.
508     * <pre>
509     *       Min: n/a
510     *       Max: n/a
511     *   Default: Color.BLACK
512     *  Identity: n/a
513     * </pre>
514     * @defaultValue BLACK
515     */
516    private ObjectProperty<Color> color;
517
518
519    public final void setColor(Color value) {
520        colorProperty().set(value);
521    }
522
523    public final Color getColor() {
524        return color == null ? Color.BLACK : color.get();
525    }
526
527    public final ObjectProperty<Color> colorProperty() {
528        if (color == null) {
529            color = new ObjectPropertyBase<Color>(Color.BLACK) {
530
531                @Override
532                public void invalidated() {
533                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
534                }
535
536                @Override
537                public Object getBean() {
538                    return DropShadow.this;
539                }
540
541                @Override
542                public String getName() {
543                    return "color";
544                }
545            };
546        }
547        return color;
548    }
549
550    /**
551     * The shadow offset in the x direction, in pixels.
552     * <pre>
553     *       Min: n/a
554     *       Max: n/a
555     *   Default: 0.0
556     *  Identity: 0.0
557     * </pre>
558     * @defaultValue 0.0
559     */
560    private DoubleProperty offsetX;
561
562    public final void setOffsetX(double value) {
563        offsetXProperty().set(value);
564
565    }
566
567    public final double getOffsetX() {
568        return offsetX == null ? 0 : offsetX.get();
569    }
570
571    public final DoubleProperty offsetXProperty() {
572        if (offsetX == null) {
573            offsetX = new DoublePropertyBase() {
574
575                @Override
576                public void invalidated() {
577                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
578                    effectBoundsChanged();
579                }
580
581                @Override
582                public Object getBean() {
583                    return DropShadow.this;
584                }
585
586                @Override
587                public String getName() {
588                    return "offsetX";
589                }
590            };
591        }
592        return offsetX;
593    }
594
595    /**
596     * The shadow offset in the y direction, in pixels.
597     * <pre>
598     *       Min: n/a
599     *       Max: n/a
600     *   Default: 0.0
601     *  Identity: 0.0
602     * </pre>
603     * @defaultValue 0.0
604     */
605    private DoubleProperty offsetY;
606
607
608    public final void setOffsetY(double value) {
609        offsetYProperty().set(value);
610    }
611
612    public final double getOffsetY() {
613        return offsetY == null ? 0 : offsetY.get();
614    }
615
616    public final DoubleProperty offsetYProperty() {
617        if (offsetY == null) {
618            offsetY = new DoublePropertyBase() {
619
620                @Override
621                public void invalidated() {
622                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
623                    effectBoundsChanged();
624                }
625
626                @Override
627                public Object getBean() {
628                    return DropShadow.this;
629                }
630
631                @Override
632                public String getName() {
633                    return "offsetY";
634                }
635            };
636        }
637        return offsetY;
638    }
639
640    private float getClampedWidth() {
641        return (float) Utils.clamp(0, getWidth(), 255);
642    }
643
644    private float getClampedHeight() {
645        return (float) Utils.clamp(0, getHeight(), 255);
646    }
647
648    private float getClampedSpread() {
649        return (float) Utils.clamp(0, getSpread(), 1);
650    }
651
652    private Color getColorInternal() {
653        Color c = getColor();
654        return c == null ? Color.BLACK : c;
655    }
656
657    private BlurType getBlurTypeInternal() {
658        BlurType bt = getBlurType();
659        return bt == null ? BlurType.THREE_PASS_BOX : bt;
660    }
661
662    @Override
663    void impl_update() {
664        Effect localInput = getInput();
665        if (localInput != null) {
666            localInput.impl_sync();
667        }
668
669        com.sun.scenario.effect.DropShadow peer =
670                (com.sun.scenario.effect.DropShadow) impl_getImpl();
671        peer.setShadowSourceInput(localInput == null ? null : localInput.impl_getImpl());
672        peer.setContentInput(localInput == null ? null : localInput.impl_getImpl());
673        peer.setGaussianWidth(getClampedWidth());
674        peer.setGaussianHeight(getClampedHeight());
675        peer.setSpread(getClampedSpread());
676        peer.setShadowMode(Toolkit.getToolkit().toShadowMode(getBlurTypeInternal()));
677        peer.setColor(Toolkit.getToolkit().toColor4f(getColorInternal()));
678        peer.setOffsetX((int) getOffsetX());
679        peer.setOffsetY((int) getOffsetY());
680    }
681
682    /**
683     * @treatAsPrivate implementation detail
684     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
685     */
686    @Deprecated
687    @Override
688    public BaseBounds impl_getBounds(BaseBounds bounds,
689                                     BaseTransform tx,
690                                     Node node,
691                                     BoundsAccessor boundsAccessor) {
692        bounds = EffectUtils.getInputBounds(bounds,
693                                            BaseTransform.IDENTITY_TRANSFORM,
694                                            node, boundsAccessor,
695                                            getInput());
696
697        int shadowX = (int) getOffsetX();
698        int shadowY = (int) getOffsetY();
699
700        BaseBounds shadowBounds = BaseBounds.getInstance(bounds.getMinX() + shadowX,
701                                                         bounds.getMinY() + shadowY,
702                                                         bounds.getMinZ(),
703                                                         bounds.getMaxX() + shadowX,
704                                                         bounds.getMaxY() + shadowY,
705                                                         bounds.getMaxZ());
706
707        shadowBounds = EffectUtils.getShadowBounds(shadowBounds, tx,
708                                                   getClampedWidth(),
709                                                   getClampedHeight(),
710                                                   getBlurTypeInternal());
711        BaseBounds contentBounds = EffectUtils.transformBounds(tx, bounds);
712        BaseBounds ret = contentBounds.deriveWithUnion(shadowBounds);
713
714        return ret;
715    }
716
717    /**
718     * 
719     * @treatAsPrivate implementation detail
720     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
721     */
722    @Deprecated
723    @Override
724    public Effect impl_copy() {
725        DropShadow d = new DropShadow(this.getBlurType(), this.getColor(), 
726                this.getRadius(), this.getSpread(), this.getOffsetX(), 
727                this.getOffsetY());
728        d.setInput(this.getInput());
729        d.setWidth(this.getWidth());
730        d.setHeight(this.getHeight());
731        return d;
732    }
733}