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.BaseBounds;
  30 import com.sun.javafx.geom.DirtyRegionContainer;
  31 import com.sun.javafx.geom.DirtyRegionPool;
  32 import com.sun.javafx.geom.Rectangle;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 
  35 /**
  36  * An effect that returns a cropped version of the input.
  37  */
  38 public class Crop extends CoreEffect {
  39 
  40     // TODO: This class should go away once we fix RT-1347...
  41 
  42     /**
  43      * Constructs a new {@code Crop} effect which crops the output of
  44      * the specified source {@code Effect} to the bounds of the default
  45      * input.
  46      *
  47      * @param source the input {@code Effect} for image data
  48      */
  49     public Crop(Effect source) {
  50         this(source, DefaultInput);
  51     }
  52 
  53     /**
  54      * Constructs a new {@code Crop} effect to crop the output of the
  55      * specified source {@code Effect} to the bounds of the specified
  56      * bounds {@code Effect}.
  57      *
  58      * @param source the input {@code Effect} for image data
  59      * @param boundsInput the input {@code Effect} that controls bounds
  60      */
  61     public Crop(Effect source, Effect boundsInput) {
  62         super(source, boundsInput);
  63         updatePeerKey("Crop");
  64     }
  65 
  66     /**
  67      * Returns the input for this {@code Effect}.
  68      *
  69      * @return the input for this {@code Effect}
  70      */
  71     public final Effect getInput() {
  72         return getInputs().get(0);
  73     }
  74 
  75     /**
  76      * Sets the source input for this {@code Effect} to a specific
  77      * {@code Effect} or to the default input if {@code input} is
  78      * {@code null}.
  79      *
  80      * @param input the input for this {@code Effect}
  81      */
  82     public void setInput(Effect input) {
  83         setInput(0, input);
  84     }
  85 
  86     /**
  87      * Returns the bounds input for this {@code Effect}.
  88      *
  89      * @return the bounds input for this {@code Effect}
  90      */
  91     public final Effect getBoundsInput() {
  92         return getInputs().get(1);
  93     }
  94 
  95     /**
  96      * Sets the bounds input for this {@code Effect}.
  97      * Sets the bounds input for this {@code Effect} to a specific
  98      * {@code Effect} or to the default input if {@code input} is
  99      * {@code null}.
 100      *
 101      * @param input the bounds input for this {@code Effect}
 102      */
 103     public void setBoundsInput(Effect input) {
 104         setInput(1, input);
 105     }
 106 
 107     private Effect getBoundsInput(Effect defaultInput) {
 108         return getDefaultedInput(1, defaultInput);
 109     }
 110 
 111     @Override
 112     public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
 113         return getBoundsInput(defaultInput).getBounds(transform, defaultInput);
 114     }
 115 
 116     /**
 117      * Transform the specified point {@code p} from the coordinate space
 118      * of the primary content input to the coordinate space of the effect
 119      * output.
 120      * In essence, this method asks the question "Which output coordinate
 121      * is most affected by the data at the specified coordinate in the
 122      * primary source input?"
 123      * <p>
 124      * The {@code Crop} effect delegates this operation to its primary
 125      * (non-bounds) input, or the {@code defaultInput} if the primary
 126      * input is {@code null}.
 127      *
 128      * @param p the point in the coordinate space of the primary content
 129      *          input to be transformed
 130      * @param defaultInput the default input {@code Effect} to be used in
 131      *                     all cases where a filter has a null input
 132      * @return the transformed point in the coordinate space of the result
 133      */
 134     @Override
 135     public Point2D transform(Point2D p, Effect defaultInput) {
 136         return getDefaultedInput(0, defaultInput).transform(p, defaultInput);
 137     }
 138 
 139     /**
 140      * Transform the specified point {@code p} from the coordinate space
 141      * of the output of the effect into the coordinate space of the
 142      * primary content input.
 143      * In essence, this method asks the question "Which source coordinate
 144      * contributes most to the definition of the output at the specified
 145      * coordinate?"
 146      * <p>
 147      * The {@code Crop} effect delegates this operation to its primary
 148      * (non-bounds) input, or the {@code defaultInput} if the primary
 149      * input is {@code null}.
 150      *
 151      * @param p the point in the coordinate space of the result output
 152      *          to be transformed
 153      * @param defaultInput the default input {@code Effect} to be used in
 154      *                     all cases where a filter has a null input
 155      * @return the untransformed point in the coordinate space of the
 156      *         primary content input
 157      */
 158     @Override
 159     public Point2D untransform(Point2D p, Effect defaultInput) {
 160         return getDefaultedInput(0, defaultInput).untransform(p, defaultInput);
 161     }
 162 
 163     @Override
 164     public ImageData filter(FilterContext fctx,
 165                             BaseTransform transform,
 166                             Rectangle outputClip,
 167                             Object renderHelper,
 168                             Effect defaultInput)
 169     {
 170         Effect input1 = getDefaultedInput(1, defaultInput);
 171         BaseBounds cropBounds = input1.getBounds(transform, defaultInput);
 172         Rectangle cropRect = new Rectangle(cropBounds);
 173         cropRect.intersectWith(outputClip);
 174         Effect input0 = getDefaultedInput(0, defaultInput);
 175         ImageData id = input0.filter(fctx, transform, cropRect, null, defaultInput);
 176         if (!id.validate(fctx)) {
 177             return new ImageData(fctx, null, null);
 178         }
 179         ImageData ret = filterImageDatas(fctx, transform, cropRect, id);
 180         id.unref();
 181         return ret;
 182     }
 183 
 184     @Override
 185     protected Rectangle getInputClip(int inputIndex,
 186                                      BaseTransform transform,
 187                                      Rectangle outputClip)
 188     {
 189         // RT-27564
 190         // TODO: Since we also crop to the "crop input" and since cropping
 191         // is a form of clipping, we could further restrict the bounds we
 192         // ask from the content input here...
 193         return outputClip;
 194     }
 195 
 196     @Override
 197     public boolean reducesOpaquePixels() {
 198         return true;
 199     }
 200 
 201     @Override
 202     public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
 203         Effect di0 = getDefaultedInput(0, defaultInput);
 204         DirtyRegionContainer drc = di0.getDirtyRegions(defaultInput, regionPool);
 205         Effect di1 = getDefaultedInput(1, defaultInput);
 206         BaseBounds cropBounds = di1.getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput);
 207         for (int i = 0; i < drc.size(); i++) {
 208             drc.getDirtyRegion(i).intersectWith(cropBounds);
 209             if (drc.checkAndClearRegion(i)) {
 210                 --i;
 211             }
 212         }
 213 
 214         return drc;
 215     }
 216 }