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 }