1 /*
   2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.scenario.effect;
  27 
  28 import com.sun.javafx.geom.Point2D;
  29 import com.sun.javafx.geom.Rectangle;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 
  32 /**
  33  * An effect that merges two inputs together into one result.  This produces
  34  * the same result as using the {@code Blend} effect with
  35  * {@code Blend.Mode.SRC_OVER} and {@code opacity=1.0}, except possibly
  36  * more efficient.
  37  */
  38 public class Merge extends CoreEffect {
  39 
  40     /**
  41      * Constructs a new {@code Merge} effect for the given inputs.
  42      *
  43      * @param bottomInput the bottom input
  44      * @param topInput the top input
  45      */
  46     public Merge(Effect bottomInput, Effect topInput) {
  47         super(bottomInput, topInput);
  48         updatePeerKey("Merge");
  49     }
  50 
  51     /**
  52      * Returns the bottom input for this {@code Effect}.
  53      *
  54      * @return the bottom input for this {@code Effect}
  55      */
  56     public final Effect getBottomInput() {
  57         return getInputs().get(0);
  58     }
  59 
  60     /**
  61      * Sets the bottom input for this {@code Effect} to a specific
  62      * {@code Effect} or to the default input if {@code input} is
  63      * {@code null}.
  64      *
  65      * @param bottomInput the bottom input for this {@code Effect}
  66      */
  67     public void setBottomInput(Effect bottomInput) {
  68         setInput(0, bottomInput);
  69     }
  70 
  71     /**
  72      * Returns the top input for this {@code Effect}.
  73      *
  74      * @return the top input for this {@code Effect}
  75      */
  76     public final Effect getTopInput() {
  77         return getInputs().get(1);
  78     }
  79 
  80     /**
  81      * Sets the top input for this {@code Effect} to a specific
  82      * {@code Effect} or to the default input if {@code input} is
  83      * {@code null}.
  84      *
  85      * @param topInput the top input for this {@code Effect}
  86      */
  87     public void setTopInput(Effect topInput) {
  88         setInput(1, topInput);
  89     }
  90 
  91     /**
  92      * Transform the specified point {@code p} from the coordinate space
  93      * of the primary content input to the coordinate space of the effect
  94      * output.
  95      * In essence, this method asks the question "Which output coordinate
  96      * is most affected by the data at the specified coordinate in the
  97      * primary source input?"
  98      * <p>
  99      * The {@code Merge} effect delegates this operation to its {@code top}
 100      * input, or the {@code defaultInput} if the {@code top} input is
 101      * {@code null}.
 102      *
 103      * @param p the point in the coordinate space of the primary content
 104      *          input to be transformed
 105      * @param defaultInput the default input {@code Effect} to be used in
 106      *                     all cases where a filter has a null input
 107      * @return the transformed point in the coordinate space of the result
 108      */
 109     @Override
 110     public Point2D transform(Point2D p, Effect defaultInput) {
 111         return getDefaultedInput(1, defaultInput).transform(p, defaultInput);
 112     }
 113 
 114     /**
 115      * Transform the specified point {@code p} from the coordinate space
 116      * of the output of the effect into the coordinate space of the
 117      * primary content input.
 118      * In essence, this method asks the question "Which source coordinate
 119      * contributes most to the definition of the output at the specified
 120      * coordinate?"
 121      * <p>
 122      * The {@code Merge} effect delegates this operation to its {@code top}
 123      * input, or the {@code defaultInput} if the {@code top} input is
 124      * {@code null}.
 125      *
 126      * @param p the point in the coordinate space of the result output
 127      *          to be transformed
 128      * @param defaultInput the default input {@code Effect} to be used in
 129      *                     all cases where a filter has a null input
 130      * @return the untransformed point in the coordinate space of the
 131      *         primary content input
 132      */
 133     @Override
 134     public Point2D untransform(Point2D p, Effect defaultInput) {
 135         return getDefaultedInput(1, defaultInput).untransform(p, defaultInput);
 136     }
 137 
 138     @Override
 139     public ImageData filter(FilterContext fctx,
 140                             BaseTransform transform,
 141                             Rectangle outputClip,
 142                             Object renderHelper,
 143                             Effect defaultInput)
 144     {
 145         Effect botinput = getDefaultedInput(0, defaultInput);
 146         Effect topinput = getDefaultedInput(1, defaultInput);
 147         ImageData botimg = botinput.filter(fctx, transform, outputClip,
 148                                            renderHelper, defaultInput);
 149         if (botimg != null) {
 150             if (!botimg.validate(fctx)) {
 151                 return new ImageData(fctx, null, null);
 152             }
 153             if (renderHelper instanceof ImageDataRenderer) {
 154                 ImageDataRenderer imgr = (ImageDataRenderer) renderHelper;
 155                 imgr.renderImage(botimg, BaseTransform.IDENTITY_TRANSFORM, fctx);
 156                 botimg.unref();
 157                 botimg = null;
 158             }
 159         }
 160         if (botimg == null) {
 161             return topinput.filter(fctx, transform, outputClip,
 162                                    renderHelper, defaultInput);
 163         }
 164         ImageData topimg = topinput.filter(fctx, transform, outputClip,
 165                                            null, defaultInput);
 166         if (!topimg.validate(fctx)) {
 167             return new ImageData(fctx, null, null);
 168         }
 169         ImageData ret = filterImageDatas(fctx, transform, outputClip,
 170                                          botimg, topimg);
 171         botimg.unref();
 172         topimg.unref();
 173         return ret;
 174     }
 175 
 176     @Override
 177     protected Rectangle getInputClip(int inputIndex,
 178                                      BaseTransform transform,
 179                                      Rectangle outputClip)
 180     {
 181         // This no longer matters since we completely override the
 182         // filter method so the FilterEffect.filter() method no longer
 183         // comes into play.
 184         throw new InternalError("Merge.getInputClip should not be called");
 185     }
 186 
 187     @Override
 188     public boolean reducesOpaquePixels() {
 189         final Effect topInput = getTopInput();
 190         final Effect bottomInput = getBottomInput();
 191         return topInput != null && topInput.reducesOpaquePixels() && bottomInput != null && bottomInput.reducesOpaquePixels();
 192     }
 193 
 194 }