1 /*
   2  * Copyright (c) 2009, 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.impl.state;
  27 
  28 import java.nio.FloatBuffer;
  29 import com.sun.scenario.effect.Effect.AccelType;
  30 import com.sun.scenario.effect.FilterContext;
  31 import com.sun.scenario.effect.impl.BufferUtil;
  32 import com.sun.scenario.effect.impl.EffectPeer;
  33 import com.sun.scenario.effect.impl.Renderer;
  34 
  35 /**
  36  * The state and implementation class for calculating 1 dimensional
  37  * linear convolution kernels for performing multi-pass box blurs.
  38  */
  39 public class BoxBlurState extends HVSeparableKernel {
  40     private int hsize;
  41     private int vsize;
  42     private int blurPasses;
  43     private FloatBuffer weights;
  44 
  45     public int getHsize() {
  46         return hsize;
  47     }
  48 
  49     public void setHsize(int hsize) {
  50         if (hsize < 0 || hsize > 255) {
  51             throw new IllegalArgumentException("Blur size must be in the range [0,255]");
  52         }
  53         this.hsize = hsize;
  54     }
  55 
  56     public int getVsize() {
  57         return vsize;
  58     }
  59 
  60     public void setVsize(int vsize) {
  61         if (vsize < 0 || vsize > 255) {
  62             throw new IllegalArgumentException("Blur size must be in the range [0,255]");
  63         }
  64         this.vsize = vsize;
  65     }
  66 
  67     public int getBlurPasses() {
  68         return blurPasses;
  69     }
  70 
  71     public void setBlurPasses(int blurPasses) {
  72         if (blurPasses < 0 || blurPasses > 3) {
  73             throw new IllegalArgumentException("Number of passes must be in the range [0,3]");
  74         }
  75         this.blurPasses = blurPasses;
  76     }
  77 
  78     public float getSpread() {
  79         return 0.0f;
  80     }
  81 
  82     @Override
  83     public boolean isNop() {
  84         return (blurPasses == 0 || (hsize <= 1 && vsize <= 1));
  85     }
  86 
  87     @Override
  88     public boolean isNop(int pass) {
  89         return (blurPasses == 0 || ((pass == 0) ? hsize : vsize) <= 1);
  90     }
  91 
  92     public int getKernelSize(int pass) {
  93         int ksize = pass == 0 ? hsize : vsize;
  94         if (ksize < 1) ksize = 1;
  95         ksize = (ksize-1) * blurPasses + 1;
  96         ksize |= 1;
  97         return ksize;
  98     }
  99 
 100     private int getScaleVal(int pass, boolean needScale) {
 101         int ksize = getKernelSize(pass);
 102         int scale = 0;
 103         while (ksize > 128) {
 104             ksize = ((ksize + 1) / 2) | 1;
 105             scale--;
 106         }
 107         return needScale ? scale : ksize;
 108     }
 109 
 110     public int getPow2Scale(int pass) {
 111         return getScaleVal(pass, true);
 112     }
 113 
 114     @Override
 115     public int getScaledKernelSize(int pass) {
 116         return getScaleVal(pass, false);
 117     }
 118 
 119     @Override
 120     public int getPow2ScaleX() {
 121         return getPow2Scale(0);
 122     }
 123 
 124     @Override
 125     public int getPow2ScaleY() {
 126         return getPow2Scale(1);
 127     }
 128 
 129     public FloatBuffer getWeights(int pass) {
 130         int klen = pass == 0 ? hsize : vsize;
 131         if (klen < 1 || blurPasses == 0) klen = 1;
 132         long ik[] = new long[klen];
 133         for (int i = 0; i < klen; i++) {
 134             ik[i] = 1;
 135         }
 136         for (int p = 1; p < blurPasses; p++) {
 137             long ik2[] = new long[ik.length + klen-1];
 138             for (int i = 0; i < ik.length; i++) {
 139                 for (int k = 0; k < klen; k++) {
 140                     ik2[i+k] += ik[i];
 141                 }
 142             }
 143             ik = ik2;
 144         }
 145         if ((ik.length & 1) == 0) {
 146             // If kernel length is odd then it is centered on a pixel.
 147             // If kernel length is even, then it is not centered on a pixel
 148             // and the weights are applied to a sample between pixels.
 149             // Instead of trying to sample between pixels we instead
 150             // distribute the weights by half a pixel by averaging
 151             // adjacent values together and lengthening the kernel by a pixel.
 152             long ik2[] = new long[ik.length + 1];
 153             for (int i = 0; i < ik.length; i++) {
 154                 ik2[i] += ik[i];
 155                 ik2[i+1] += ik[i];
 156             }
 157             ik = ik2;
 158         }
 159         int scale = getPow2Scale(pass);
 160         while (scale < 0) {
 161             int newlen = ((ik.length + 1) / 2) | 1;
 162             int skewi = (newlen * 2 - ik.length) / 2;
 163             long ik2[] = new long[newlen];
 164             for (int i = 0; i < ik.length; i++) {
 165                 ik2[skewi / 2] += ik[i];
 166                 skewi++;
 167                 ik2[skewi / 2] += ik[i];
 168             }
 169             ik = ik2;
 170             scale++;
 171         }
 172         double sum = 0.0;
 173         for (int i = 0; i < ik.length; i++) {
 174             sum += ik[i];
 175         }
 176         // We need to apply the spread on only one pass
 177         // Prefer pass1 if r1 is not trivial
 178         // Otherwise use pass 0 so that it doesn't disappear
 179         int spreadpass = (vsize > 1) ? 1 : 0;
 180         float s = (pass == spreadpass) ? getSpread() : 0f;
 181         sum += (ik[0] - sum) * s;
 182 
 183         if (weights == null) {
 184             // peersize(MAX_KERNEL_SIZE) rounded up to the next multiple of 4
 185             int maxbufsize = LinearConvolveKernel.MAX_KERNEL_SIZE;
 186             maxbufsize = LinearConvolveKernel.getPeerSize(maxbufsize);
 187             maxbufsize = (maxbufsize + 3) & (~3);
 188             weights = BufferUtil.newFloatBuffer(maxbufsize);
 189         }
 190         weights.clear();
 191         for (int i = 0; i < ik.length; i++) {
 192             weights.put((float) (ik[i] / sum));
 193         }
 194         int limit = getPeerSize(ik.length);
 195         while (weights.position() < limit) {
 196             weights.put(0f);
 197         }
 198         weights.limit(limit);
 199         weights.rewind();
 200         return weights;
 201     }
 202 
 203     @Override
 204     public EffectPeer getPeer(Renderer r, FilterContext fctx, int pass) {
 205         int ksize = getScaledKernelSize(pass);
 206         if (ksize <= 1) {
 207             // The result of ksize being too <= 1 is a NOP
 208             // so we return null here to skip the corresponding
 209             // filter pass.
 210             return null;
 211         }
 212         int psize = getPeerSize(ksize);
 213         AccelType actype = r.getAccelType();
 214         String name;
 215         switch (actype) {
 216             case NONE:
 217             case SIMD:
 218                 name = "BoxBlur";
 219                 break;
 220             default:
 221                 name = "LinearConvolve";
 222                 break;
 223         }
 224         EffectPeer peer = r.getPeerInstance(fctx, name, psize);
 225         return peer;
 226     }
 227 }