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.RectBounds;
  31 import com.sun.javafx.geom.Rectangle;
  32 import com.sun.javafx.geom.transform.BaseTransform;
  33 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  34 
  35 /**
  36  * The implementation base class for {@link Effect} subclasses that operate
  37  * by filtering the inputs at the pixel level.
  38  */
  39 public abstract class FilterEffect extends Effect {
  40     protected FilterEffect() {
  41         super();
  42     }
  43 
  44     protected FilterEffect(Effect input) {
  45         super(input);
  46     }
  47 
  48     protected FilterEffect(Effect input1, Effect input2) {
  49         super(input1, input2);
  50     }
  51 
  52     public boolean operatesInUserSpace() {
  53         return false;
  54     }
  55 
  56     public BaseBounds getBounds(BaseTransform transform,
  57                               Effect defaultInput)
  58     {
  59         int numinputs = getNumInputs();
  60         BaseTransform inputtx = transform;
  61         if (operatesInUserSpace()) {
  62             inputtx = BaseTransform.IDENTITY_TRANSFORM;
  63         }
  64         BaseBounds ret;
  65         if (numinputs == 1) {
  66             Effect input = getDefaultedInput(0, defaultInput);
  67             ret = input.getBounds(inputtx, defaultInput);
  68         } else {
  69             BaseBounds inputBounds[] = new BaseBounds[numinputs];
  70             for (int i = 0; i < numinputs; i++) {
  71                 Effect input = getDefaultedInput(i, defaultInput);
  72                 inputBounds[i] = input.getBounds(inputtx, defaultInput);
  73             }
  74             ret = combineBounds(inputBounds);
  75         }
  76         if (inputtx != transform) {
  77             ret = transformBounds(transform, ret);
  78         }
  79         return ret;
  80     }
  81 
  82     protected abstract Rectangle getInputClip(int inputIndex,
  83                                               BaseTransform transform,
  84                                               Rectangle outputBounds);
  85 
  86     protected static Rectangle untransformClip(BaseTransform transform,
  87                                                Rectangle clip)
  88     {
  89         if (transform.isIdentity() || clip == null || clip.isEmpty()) {
  90             return clip;
  91         }
  92         // We are asked to produce samples for the pixels in the
  93         // Rectangular clip.  The samples requested are delivered for
  94         // the centers of the pixels for every pixel in that range.
  95         // Thus, we need valid data for the clip inset by 0.5 pixels
  96         // all around.
  97         // But, when we untransform, we need to make sure that the data
  98         // we provide can be used to provide a valid sample for each of
  99         // those points.  If the mapped sample coordinate falls on a
 100         // non-integer coordinate then we need the data for the 4 pixels
 101         // around that point.  Thus, we need a sample for the pixel that it
 102         // falls on, and potentially a sample for the next pixel over if
 103         // we are within 0.5 pixels of the edge of those border pixels.
 104         // The full operation is then:
 105         //     clip.inset(0.5)       // reduce to requested pixel centers
 106         //     tx.untransform(clip)  // untransform to new source space
 107         //     clip.outset(0.5)      // expand for bilinear interpolation
 108         //     clip.roundtopixels()  // clamp to pixel edges
 109         Rectangle transformedBounds = new Rectangle();
 110         if (transform.isTranslateOrIdentity()) {
 111             // In this case the inset and outset cancel each other out
 112             // and the floor(x0,y0) and ceil(x1,y1) are enough to provide
 113             // whatever padding is needed.
 114             transformedBounds.setBounds(clip);
 115             double tx = -transform.getMxt();
 116             double ty = -transform.getMyt();
 117             int itx = (int) Math.floor(tx);
 118             int ity = (int) Math.floor(ty);
 119             transformedBounds.translate(itx, ity);
 120             if (itx != tx) {
 121                 // floor(x0) is 1 more pixel away from ceil(x1)
 122                 transformedBounds.width++;
 123             }
 124             if (ity != ty) {
 125                 // floor(y0) is 1 more pixel away from ceil(y1)
 126                 transformedBounds.height++;
 127             }
 128             return transformedBounds;
 129         }
 130         RectBounds b = new RectBounds(clip);
 131         try {
 132             b.grow(-0.5f, -0.5f);
 133             b = (RectBounds) transform.inverseTransform(b, b);
 134             b.grow(0.5f, 0.5f);
 135             transformedBounds.setBounds(b);
 136         } catch (NoninvertibleTransformException e) {
 137             // Non-invertible means the transform has collapsed onto
 138             // a point or line and so the results of the effect are
 139             // not visible so we can use the empty bounds object we
 140             // created for transformedBounds.  Ideally this would be
 141             // checked further up in the chain, but in case we get here
 142             // we might as well do as little work as we can.
 143         }
 144         return transformedBounds;
 145     }
 146 
 147     @Override
 148     public ImageData filter(FilterContext fctx,
 149                             BaseTransform transform,
 150                             Rectangle outputClip,
 151                             Object renderHelper,
 152                             Effect defaultInput)
 153     {
 154         int numinputs = getNumInputs();
 155         ImageData inputDatas[] = new ImageData[numinputs];
 156         Rectangle inputClip;
 157         BaseTransform inputtx;
 158         if (operatesInUserSpace()) {
 159             inputClip = untransformClip(transform, outputClip);
 160             inputtx = BaseTransform.IDENTITY_TRANSFORM;
 161         } else {
 162             inputClip = outputClip;
 163             inputtx = transform;
 164         }
 165         for (int i = 0; i < numinputs; i++) {
 166             Effect input = getDefaultedInput(i, defaultInput);
 167             inputDatas[i] =
 168                 input.filter(fctx, inputtx,
 169                              getInputClip(i, inputtx, inputClip),
 170                              null, defaultInput);
 171             if (!inputDatas[i].validate(fctx)) {
 172                 for (int j = 0; j <= i; j++) {
 173                     inputDatas[j].unref();
 174                 }
 175                 return new ImageData(fctx, null, null);
 176             }
 177         }
 178         ImageData ret = filterImageDatas(fctx, inputtx, inputClip, inputDatas);
 179         for (int i = 0; i < numinputs; i++) {
 180             inputDatas[i].unref();
 181         }
 182         if (inputtx != transform) {
 183             if (renderHelper instanceof ImageDataRenderer) {
 184                 ImageDataRenderer renderer = (ImageDataRenderer) renderHelper;
 185                 renderer.renderImage(ret, transform, fctx);
 186                 ret.unref();
 187                 ret = null;
 188             } else {
 189                 ret = ret.transform(transform);
 190             }
 191         }
 192         return ret;
 193     }
 194 
 195     @Override
 196     public Point2D transform(Point2D p, Effect defaultInput) {
 197         return getDefaultedInput(0, defaultInput).transform(p, defaultInput);
 198     }
 199 
 200     @Override
 201     public Point2D untransform(Point2D p, Effect defaultInput) {
 202         return getDefaultedInput(0, defaultInput).untransform(p, defaultInput);
 203     }
 204 
 205     protected abstract ImageData filterImageDatas(FilterContext fctx,
 206                                                   BaseTransform transform,
 207                                                   Rectangle outputClip,
 208                                                   ImageData... inputDatas);
 209 }