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.Observable;
029import javafx.beans.property.BooleanProperty;
030import javafx.beans.property.BooleanPropertyBase;
031import javafx.beans.property.DoubleProperty;
032import javafx.beans.property.DoublePropertyBase;
033import javafx.beans.property.ObjectProperty;
034import javafx.beans.property.ObjectPropertyBase;
035import javafx.scene.Node;
036
037import com.sun.javafx.effect.EffectDirtyBits;
038import com.sun.javafx.effect.EffectUtils;
039import com.sun.javafx.geom.BaseBounds;
040import com.sun.javafx.geom.transform.BaseTransform;
041import com.sun.javafx.scene.BoundsAccessor;
042
043/**
044 * An effect that shifts each pixel by a distance specified by
045 * the first two bands of of the specified {@link FloatMap}.
046 * For each pixel in the output, the corresponding data from the
047 * {@code mapData} is retrieved, scaled and offset by the {@code scale}
048 * and {@code offset} attributes, scaled again by the size of the
049 * source input image and used as an offset from the destination pixel
050 * to retrieve the pixel data from the source input.
051 * <pre>
052 *     dst[x,y] = src[(x,y) + (offset+scale*map[x,y])*(srcw,srch)]
053 * </pre>
054 * A value of {@code (0.0,&nbsp;0.0)} would specify no offset for the
055 * pixel data whereas a value of {@code (0.5,&nbsp;0.5)} would specify
056 * an offset of half of the source image size.
057 * <p>
058 * <b>Note</b> that the mapping is the offset from a destination pixel to
059 * the source pixel location from which it is sampled which means that
060 * filling the map with all values of {@code 0.5} would displace the
061 * image by half of its size towards the upper left since each destination
062 * pixel would contain the data that comes from the source pixel below and
063 * to the right of it.
064 * <p>
065 * Also note that this effect does not adjust the coordinates of input
066 * events or any methods that measure containment on a {@code Node}.
067 * The results of mouse picking and the containment methods are undefined
068 * when a {@code Node} has a {@code DisplacementMap} effect in place.
069 *
070 * <p>
071 * Example:
072 * <pre><code>
073 * int width = 220;
074 * int height = 100;
075 * 
076 * FloatMap floatMap = new FloatMap();
077 * floatMap.setWidth(width);
078 * floatMap.setHeight(height);
079 *
080 * for (int i = 0; i < width; i++) {
081 *     double v = (Math.sin(i / 20.0 * Math.PI) - 0.5) / 40.0;
082 *     for (int j = 0; j < height; j++) {
083 *         floatMap.setSamples(i, j, 0.0f, (float) v);
084 *     }
085 * }
086 *
087 * DisplacementMap displacementMap = new DisplacementMap();
088 * displacementMap.setMapData(floatMap);
089 *
090 * Text text = new Text();
091 * text.setX(40.0);
092 * text.setY(80.0);
093 * text.setText("Wavy Text");
094 * text.setFill(Color.web("0x3b596d"));
095 * text.setFont(Font.font(null, FontWeight.BOLD, 50));
096 * text.setEffect(displacementMap);
097 *
098 * </pre></code>
099 *
100 * <p> The code above produces the following: </p>
101 * <p> <img src="doc-files/displacementmap.png"/> </p>
102 */
103public class DisplacementMap extends Effect {
104    @Override
105    com.sun.scenario.effect.DisplacementMap impl_createImpl() {
106        return new com.sun.scenario.effect.DisplacementMap(
107                            new com.sun.scenario.effect.FloatMap(1, 1),
108                            com.sun.scenario.effect.Effect.DefaultInput);
109    };
110
111    /**
112     * Creates a new instance of DisplacementMap with default parameters.
113     */
114    public DisplacementMap() {
115        setMapData(new FloatMap(1, 1));
116    }
117
118    /**
119     * Creates a new instance of DisplacementMap with the specified mapData.
120     * @param mapData the map data for this displacement map effect
121     */
122    public DisplacementMap(FloatMap mapData) {
123        setMapData(mapData);
124    }
125
126    /**
127     * Creates a new instance of DisplacementMap with the specified mapData,
128     * offsetX, offsetY, scaleX, and scaleY.
129     * @param mapData the map data for this displacement map effect
130     * @param offsetX the offset by which all x coordinate offset values in the
131     * {@code FloatMap} are displaced after they are scaled
132     * @param offsetY the offset by which all y coordinate offset values in the
133     * {@code FloatMap} are displaced after they are scaled
134     * @param scaleX the scale factor by which all x coordinate offset values in the
135     * {@code FloatMap} are multiplied
136     * @param scaleY the scale factor by which all y coordinate offset values in the
137     * {@code FloatMap} are multiplied
138     */
139    public DisplacementMap(FloatMap mapData, 
140                           double offsetX, double offsetY,
141                           double scaleX, double scaleY) {
142        setMapData(mapData);
143        setOffsetX(offsetX);
144        setOffsetY(offsetY);
145        setScaleX(scaleX);
146        setScaleY(scaleY);
147    }
148
149    /**
150     * The input for this {@code Effect}.
151     * If set to {@code null}, or left unspecified, a graphical image of
152     * the {@code Node} to which the {@code Effect} is attached will be
153     * used as the input.
154     * @defaultValue null
155     */
156    private ObjectProperty<Effect> input;
157
158
159    public final void setInput(Effect value) {
160        inputProperty().set(value);
161    }
162
163    public final Effect getInput() {
164        return input == null ? null : input.get();
165    }
166
167    public final ObjectProperty<Effect> inputProperty() {
168        if (input == null) {
169            input = new EffectInputProperty("input");
170        }
171        return input;
172    }
173
174    @Override
175    boolean impl_checkChainContains(Effect e) {
176        Effect localInput = getInput();
177        if (localInput == null)
178            return false;
179        if (localInput == e)
180            return true;
181        return localInput.impl_checkChainContains(e);
182    }
183
184    private final FloatMap defaultMap = new FloatMap(1, 1);
185
186    /**
187     * The map data for this {@code Effect}.
188     * @defaultValue an empty map
189     */
190    private ObjectProperty<FloatMap> mapData;
191
192
193    public final void setMapData(FloatMap value) {
194        mapDataProperty().set(value);
195    }
196
197    public final FloatMap getMapData() {
198        return mapData == null ? null : mapData.get();
199    }
200
201    public final ObjectProperty<FloatMap> mapDataProperty() {
202        if (mapData == null) {
203            mapData = new ObjectPropertyBase<FloatMap>() {
204
205                @Override
206                public void invalidated() {
207                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
208                    effectBoundsChanged();
209                }
210
211                @Override
212                public Object getBean() {
213                    return DisplacementMap.this;
214                }
215
216                @Override
217                public String getName() {
218                    return "mapData";
219                }
220            };
221        }
222        return mapData;
223    }
224
225    private final MapDataChangeListener mapDataChangeListener = new MapDataChangeListener();
226
227    private class MapDataChangeListener extends EffectChangeListener {
228        FloatMap mapData;
229
230        public void register(FloatMap value) {
231            mapData = value;
232            super.register(mapData == null ? null : mapData.effectDirtyProperty());
233        }
234
235        @Override
236        public void invalidated(Observable valueModel) {
237            if (mapData.impl_isEffectDirty()) {
238                markDirty(EffectDirtyBits.EFFECT_DIRTY);
239                effectBoundsChanged();
240            }
241        }
242    };
243
244    /**
245     * The scale factor by which all x coordinate offset values in the
246     * {@code FloatMap} are multiplied.
247     * <pre>
248     *       Min: n/a
249     *       Max: n/a
250     *   Default: 1.0
251     *  Identity: 1.0
252     * </pre>
253     * @defaultValue 1.0
254     */
255    private DoubleProperty scaleX;
256
257
258    public final void setScaleX(double value) {
259        scaleXProperty().set(value);
260    }
261
262    public final double getScaleX() {
263        return scaleX == null ? 1 : scaleX.get();
264    }
265
266    public final DoubleProperty scaleXProperty() {
267        if (scaleX == null) {
268            scaleX = new DoublePropertyBase(1) {
269
270                @Override
271                public void invalidated() {
272                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
273                }
274
275                @Override
276                public Object getBean() {
277                    return DisplacementMap.this;
278                }
279
280                @Override
281                public String getName() {
282                    return "scaleX";
283                }
284            };
285        }
286        return scaleX;
287    }
288
289    /**
290     * The scale factor by which all y coordinate offset values in the
291     * {@code FloatMap} are multiplied.
292     * <pre>
293     *       Min: n/a
294     *       Max: n/a
295     *   Default: 1.0
296     *  Identity: 1.0
297     * </pre>
298     * @defaultValue 1.0
299     */
300    private DoubleProperty scaleY;
301
302
303    public final void setScaleY(double value) {
304        scaleYProperty().set(value);
305    }
306
307    public final double getScaleY() {
308        return scaleY == null ? 1 : scaleY.get();
309    }
310
311    public final DoubleProperty scaleYProperty() {
312        if (scaleY == null) {
313            scaleY = new DoublePropertyBase(1) {
314
315                @Override
316                public void invalidated() {
317                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
318                }
319
320                @Override
321                public Object getBean() {
322                    return DisplacementMap.this;
323                }
324
325                @Override
326                public String getName() {
327                    return "scaleY";
328                }
329            };
330        }
331        return scaleY;
332    }
333
334    /**
335     * The offset by which all x coordinate offset values in the
336     * {@code FloatMap} are displaced after they are scaled.
337     * <pre>
338     *       Min: n/a
339     *       Max: n/a
340     *   Default: 0.0
341     *  Identity: 0.0
342     * </pre>
343     * @defaultValue 0.0
344     */
345    private DoubleProperty offsetX;
346
347
348    public final void setOffsetX(double value) {
349        offsetXProperty().set(value);
350    }
351
352    public final double getOffsetX() {
353        return offsetX == null ? 0 : offsetX.get();
354    }
355
356    public final DoubleProperty offsetXProperty() {
357        if (offsetX == null) {
358            offsetX = new DoublePropertyBase() {
359
360                @Override
361                public void invalidated() {
362                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
363                }
364
365                @Override
366                public Object getBean() {
367                    return DisplacementMap.this;
368                }
369
370                @Override
371                public String getName() {
372                    return "offsetX";
373                }
374            };
375        }
376        return offsetX;
377    }
378
379    /**
380     * The offset by which all y coordinate offset values in the
381     * {@code FloatMap} are displaced after they are scaled.
382     * <pre>
383     *       Min: n/a
384     *       Max: n/a
385     *   Default: 0.0
386     *  Identity: 0.0
387     * </pre>
388     * @defaultValue 0.0
389     */
390    private DoubleProperty offsetY;
391
392
393    public final void setOffsetY(double value) {
394        offsetYProperty().set(value);
395    }
396
397    public final double getOffsetY() {
398        return offsetY == null ? 0 : offsetY.get();
399    }
400
401    public final DoubleProperty offsetYProperty() {
402        if (offsetY == null) {
403            offsetY = new DoublePropertyBase() {
404
405                @Override
406                public void invalidated() {
407                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
408                }
409
410                @Override
411                public Object getBean() {
412                    return DisplacementMap.this;
413                }
414
415                @Override
416                public String getName() {
417                    return "offsetY";
418                }
419            };
420        }
421        return offsetY;
422    }
423
424    /**
425     * Defines whether values taken from outside the edges of the map
426     * "wrap around" or not.
427     * <pre>
428     *       Min:  n/a
429     *       Max:  n/a
430     *   Default: false
431     *  Identity:  n/a
432     * </pre>
433     * @defaultValue false
434     */
435    private BooleanProperty wrap;
436
437
438    public final void setWrap(boolean value) {
439        wrapProperty().set(value);
440    }
441
442    public final boolean isWrap() {
443        return wrap == null ? false : wrap.get();
444    }
445
446    public final BooleanProperty wrapProperty() {
447        if (wrap == null) {
448            wrap = new BooleanPropertyBase() {
449
450                @Override
451                public void invalidated() {
452                    markDirty(EffectDirtyBits.EFFECT_DIRTY);
453                }
454
455                @Override
456                public Object getBean() {
457                    return DisplacementMap.this;
458                }
459
460                @Override
461                public String getName() {
462                    return "wrap";
463                }
464            };
465        }
466        return wrap;
467    }
468
469    @Override
470    void impl_update() {
471        Effect localInput = getInput();
472        if (localInput != null) {
473            localInput.impl_sync();
474        }
475
476        com.sun.scenario.effect.DisplacementMap peer =
477                (com.sun.scenario.effect.DisplacementMap) impl_getImpl();
478        peer.setContentInput(localInput == null ? null : localInput.impl_getImpl());
479        FloatMap localMapData = getMapData();
480        mapDataChangeListener.register(localMapData);
481        if (localMapData != null) {
482            localMapData.impl_sync();
483            peer.setMapData(localMapData.getImpl());
484        } else {
485            defaultMap.impl_sync();
486            peer.setMapData(defaultMap.getImpl());
487        }
488        peer.setScaleX((float)getScaleX());
489        peer.setScaleY((float)getScaleY());
490        peer.setOffsetX((float)getOffsetX());
491        peer.setOffsetY((float)getOffsetY());
492        peer.setWrap(isWrap());
493    }
494
495    /**
496     * @treatAsPrivate implementation detail
497     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
498     */
499    @Deprecated
500    @Override
501    public BaseBounds impl_getBounds(BaseBounds bounds,
502                                     BaseTransform tx,
503                                     Node node,
504                                     BoundsAccessor boundsAccessor) {
505        bounds = EffectUtils.getInputBounds(bounds,
506                                            BaseTransform.IDENTITY_TRANSFORM,
507                                            node, boundsAccessor,
508                                            getInput());
509        return EffectUtils.transformBounds(tx, bounds);
510    }
511    
512    /**
513     * 
514     * @treatAsPrivate implementation detail
515     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
516     */
517    @Deprecated
518    @Override
519    public Effect impl_copy() {    
520        DisplacementMap dm = new DisplacementMap(this.getMapData().impl_copy(), 
521                this.getOffsetX(), this.getOffsetY(), this.getScaleX(), 
522                this.getScaleY());
523        dm.setInput(this.getInput());
524        return dm;
525    }
526}