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
17package android.filterpacks.imageproc;
18
19import android.filterfw.core.Filter;
20import android.filterfw.core.FilterContext;
21import android.filterfw.core.Frame;
22import android.filterfw.core.FrameFormat;
23import android.filterfw.core.GenerateFieldPort;
24import android.filterfw.core.KeyValueMap;
25import android.filterfw.core.NativeProgram;
26import android.filterfw.core.NativeFrame;
27import android.filterfw.core.Program;
28import android.filterfw.core.ShaderProgram;
29import android.filterfw.format.ImageFormat;
30
31import java.util.Date;
32import java.util.Random;
33
34public class BlackWhiteFilter extends Filter {
35
36    @GenerateFieldPort(name = "black", hasDefault = true)
37    private float mBlack = 0f;
38
39    @GenerateFieldPort(name = "white", hasDefault = true)
40    private float mWhite = 1f;
41
42    @GenerateFieldPort(name = "tile_size", hasDefault = true)
43    private int mTileSize = 640;
44
45    private Program mProgram;
46    private Random mRandom;
47
48    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
49
50    private final String mBlackWhiteShader =
51            "precision mediump float;\n" +
52            "uniform sampler2D tex_sampler_0;\n" +
53            "uniform vec2 seed;\n" +
54            "uniform float black;\n" +
55            "uniform float scale;\n" +
56            "uniform float stepsize;\n" +
57            "varying vec2 v_texcoord;\n" +
58            "float rand(vec2 loc) {\n" +
59            // Compute sin(theta), theta = 12.9898 x + 78.233y
60            // because floating point has limited range, make theta = theta1 + theta2
61            // where theta1 = 12x + 78y and theta2 = 0.9898x + 0.233y)
62            // Note that theta1 and theta2 cover diffent range of theta.
63            "  float theta1 = dot(loc, vec2(0.9898, 0.233));\n" +
64            "  float theta2 = dot(loc, vec2(12.0, 78.0));\n" +
65            // Use the property sin(theta) = cos(theta1)*sin(theta2)+sin(theta1)*cos(theta2)
66            // this approach also increases the precisions of sin(theta)
67            "  float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" +
68            // fract(43758.5453 * x) = fract(43758 * x + 0.5453 * x)
69            // keep value of part1 in range: (2^-14 to 2^14). Since 43758 = 117 * 374
70            // fract(43758 * sin(theta)) = mod(221 * mod(198*sin(theta), 1.0), 1.0)
71            // also to keep as much decimal digits, use the property
72            // mod(mod(198*sin(theta)) = mod(mod(197*sin(theta) + sin(theta))
73            "  float temp = mod(197.0 * value, 1.0) + value;\n" +
74            "  float part1 = mod(220.0 * temp, 1.0) + temp;\n" +
75            "  float part2 = value * 0.5453;\n" +
76            "  float part3 = cos(theta1 + theta2) * 0.43758;\n" +
77            "  return fract(part1 + part2 + part3);\n" +
78            "}\n" +
79            "void main() {\n" +
80            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
81            "  float dither = rand(v_texcoord + seed);\n" +
82            "  vec3 xform = clamp((color.rgb - black) * scale, 0.0, 1.0);\n" +
83            "  vec3 temp = clamp((color.rgb + stepsize - black) * scale, 0.0, 1.0);\n" +
84            "  vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" +
85            "  gl_FragColor = vec4(new_color, color.a);\n" +
86            "}\n";
87
88    public BlackWhiteFilter(String name) {
89        super(name);
90        mRandom = new Random(new Date().getTime());
91    }
92
93    @Override
94    public void setupPorts() {
95        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
96        addOutputBasedOnInput("image", "image");
97    }
98
99    @Override
100    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
101        return inputFormat;
102    }
103
104    public void initProgram(FilterContext context, int target) {
105        switch (target) {
106            case FrameFormat.TARGET_GPU:
107                ShaderProgram shaderProgram = new ShaderProgram(context, mBlackWhiteShader);
108                shaderProgram.setMaximumTileSize(mTileSize);
109                mProgram = shaderProgram;
110                updateParameters();
111                break;
112
113            default:
114                throw new RuntimeException("Filter Sharpen does not support frames of " +
115                    "target " + target + "!");
116        }
117        mTarget = target;
118    }
119
120    private void updateParameters() {
121        float scale = (mBlack != mWhite) ? 1.0f / (mWhite - mBlack) : 2000f;
122        float stepsize = 1.0f / 255.0f;
123        mProgram.setHostValue("black", mBlack);
124        mProgram.setHostValue("scale", scale);
125        mProgram.setHostValue("stepsize", stepsize);
126
127        float seed[] = { mRandom.nextFloat(), mRandom.nextFloat() };
128        mProgram.setHostValue("seed", seed);
129    }
130
131    @Override
132    public void fieldPortValueUpdated(String name, FilterContext context) {
133        if (mProgram != null) {
134            updateParameters();
135        }
136    }
137
138    @Override
139    public void process(FilterContext context) {
140        // Get input frame
141        Frame input = pullInput("image");
142        FrameFormat inputFormat = input.getFormat();
143
144        // Create program if not created already
145        if (mProgram == null || inputFormat.getTarget() != mTarget) {
146            initProgram(context, inputFormat.getTarget());
147        }
148
149        // Create output frame
150        Frame output = context.getFrameManager().newFrame(inputFormat);
151
152        // Process
153        mProgram.process(input, output);
154
155        // Push output
156        pushOutput("image", output);
157
158        // Release pushed frame
159        output.release();
160    }
161}
162