Home | History | Annotate | Download | only in imageproc
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.filterpacks.imageproc;
     18 
     19 import android.filterfw.core.Filter;
     20 import android.filterfw.core.FilterContext;
     21 import android.filterfw.core.Frame;
     22 import android.filterfw.core.FrameFormat;
     23 import android.filterfw.core.GenerateFieldPort;
     24 import android.filterfw.core.KeyValueMap;
     25 import android.filterfw.core.NativeProgram;
     26 import android.filterfw.core.NativeFrame;
     27 import android.filterfw.core.Program;
     28 import android.filterfw.core.ShaderProgram;
     29 import android.filterfw.format.ImageFormat;
     30 import android.filterfw.geometry.Quad;
     31 import android.filterfw.geometry.Point;
     32 
     33 import java.util.Date;
     34 import java.util.Random;
     35 
     36 public class GrainFilter extends Filter {
     37 
     38     private static final int RAND_THRESHOLD = 128;
     39 
     40     @GenerateFieldPort(name = "strength", hasDefault = true)
     41     private float mScale = 0f;
     42 
     43     @GenerateFieldPort(name = "tile_size", hasDefault = true)
     44     private int mTileSize = 640;
     45 
     46     private Program mGrainProgram;
     47     private Program mNoiseProgram;
     48 
     49     private int mWidth = 0;
     50     private int mHeight = 0;
     51     private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
     52 
     53     private Random mRandom;
     54 
     55     private final String mNoiseShader =
     56             "precision mediump float;\n" +
     57             "uniform vec2 seed;\n" +
     58             "varying vec2 v_texcoord;\n" +
     59             "float rand(vec2 loc) {\n" +
     60             "  float theta1 = dot(loc, vec2(0.9898, 0.233));\n" +
     61             "  float theta2 = dot(loc, vec2(12.0, 78.0));\n" +
     62             "  float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" +
     63             // keep value of part1 in range: (2^-14 to 2^14).
     64             "  float temp = mod(197.0 * value, 1.0) + value;\n" +
     65             "  float part1 = mod(220.0 * temp, 1.0) + temp;\n" +
     66             "  float part2 = value * 0.5453;\n" +
     67             "  float part3 = cos(theta1 + theta2) * 0.43758;\n" +
     68             "  return fract(part1 + part2 + part3);\n" +
     69             "}\n" +
     70             "void main() {\n" +
     71             "  gl_FragColor = vec4(rand(v_texcoord + seed), 0.0, 0.0, 1.0);\n" +
     72             "}\n";
     73 
     74     private final String mGrainShader =
     75             "precision mediump float;\n" +
     76             "uniform sampler2D tex_sampler_0;\n" +
     77             "uniform sampler2D tex_sampler_1;\n" +
     78             "uniform float scale;\n" +
     79             "uniform float stepX;\n" +
     80             "uniform float stepY;\n" +
     81             "varying vec2 v_texcoord;\n" +
     82             "void main() {\n" +
     83             "  float noise = texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, -stepY)).r * 0.224;\n" +
     84             "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, stepY)).r * 0.224;\n" +
     85             "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, -stepY)).r * 0.224;\n" +
     86             "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, stepY)).r * 0.224;\n" +
     87             "  noise += 0.4448;\n" +
     88             "  noise *= scale;\n" +
     89             "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
     90             "  float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" +
     91             "  float mask = (1.0 - sqrt(energy));\n" +
     92             "  float weight = 1.0 - 1.333 * mask * noise;\n" +
     93             "  gl_FragColor = vec4(color.rgb * weight, color.a);\n" +
     94             "}\n";
     95 
     96     public GrainFilter(String name) {
     97         super(name);
     98         mRandom = new Random(new Date().getTime());
     99     }
    100 
    101     @Override
    102     public void setupPorts() {
    103         addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
    104         addOutputBasedOnInput("image", "image");
    105     }
    106 
    107     @Override
    108     public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
    109         return inputFormat;
    110     }
    111 
    112     public void initProgram(FilterContext context, int target) {
    113         switch (target) {
    114             case FrameFormat.TARGET_GPU:
    115                 ShaderProgram shaderProgram = new ShaderProgram(context, mNoiseShader);
    116                 shaderProgram.setMaximumTileSize(mTileSize);
    117                 mNoiseProgram = shaderProgram;
    118 
    119                 shaderProgram = new ShaderProgram(context, mGrainShader);
    120                 shaderProgram.setMaximumTileSize(mTileSize);
    121                 mGrainProgram = shaderProgram;
    122                 break;
    123 
    124             default:
    125                 throw new RuntimeException("Filter Sharpen does not support frames of " +
    126                     "target " + target + "!");
    127         }
    128         mTarget = target;
    129     }
    130 
    131     private void updateParameters() {
    132         float seed[] = { mRandom.nextFloat(), mRandom.nextFloat() };
    133         mNoiseProgram.setHostValue("seed", seed);
    134 
    135         mGrainProgram.setHostValue("scale", mScale);
    136     }
    137 
    138     private void updateFrameSize(int width, int height) {
    139         mWidth = width;
    140         mHeight = height;
    141 
    142         if (mGrainProgram != null) {
    143             mGrainProgram.setHostValue("stepX", 0.5f / mWidth);
    144             mGrainProgram.setHostValue("stepY", 0.5f / mHeight);
    145             updateParameters();
    146         }
    147     }
    148 
    149     @Override
    150     public void fieldPortValueUpdated(String name, FilterContext context) {
    151         if (mGrainProgram != null && mNoiseProgram != null) {
    152             updateParameters();
    153         }
    154     }
    155 
    156     @Override
    157     public void process(FilterContext context) {
    158         // Get input frame
    159         Frame input = pullInput("image");
    160         FrameFormat inputFormat = input.getFormat();
    161 
    162         FrameFormat noiseFormat = ImageFormat.create(inputFormat.getWidth() / 2,
    163                                                      inputFormat.getHeight() / 2,
    164                                                      ImageFormat.COLORSPACE_RGBA,
    165                                                      FrameFormat.TARGET_GPU);
    166 
    167         // Create noise frame
    168         Frame noiseFrame = context.getFrameManager().newFrame(inputFormat);
    169 
    170         // Create output frame
    171         Frame output = context.getFrameManager().newFrame(inputFormat);
    172 
    173         // Create program if not created already
    174         if (mNoiseProgram == null || mGrainProgram == null || inputFormat.getTarget() != mTarget) {
    175             initProgram(context, inputFormat.getTarget());
    176             updateParameters();
    177         }
    178 
    179         // Check if the frame size has changed
    180         if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
    181             updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight());
    182         }
    183 
    184         Frame[] empty = {};
    185         mNoiseProgram.process(empty, noiseFrame);
    186 
    187         // Process
    188         Frame[] inputs = {input, noiseFrame};
    189         mGrainProgram.process(inputs, output);
    190 
    191         // Push output
    192         pushOutput("image", output);
    193 
    194         // Release pushed frame
    195         output.release();
    196         noiseFrame.release();
    197     }
    198 }
    199