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.Program;
25import android.filterfw.core.ShaderProgram;
26import android.filterfw.format.ImageFormat;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
29import android.graphics.Color;
30import android.graphics.Paint;
31
32/**
33 * @hide
34 */
35public class RedEyeFilter extends Filter {
36
37    private static final float RADIUS_RATIO = 0.06f;
38    private static final float MIN_RADIUS = 10.0f;
39    private static final float DEFAULT_RED_INTENSITY = 1.30f;
40
41    @GenerateFieldPort(name = "centers")
42    private float[] mCenters;
43
44    @GenerateFieldPort(name = "tile_size", hasDefault = true)
45    private int mTileSize = 640;
46
47    private Frame mRedEyeFrame;
48    private Bitmap mRedEyeBitmap;
49
50    private final Canvas mCanvas = new Canvas();
51    private final Paint mPaint = new Paint();
52
53    private float mRadius;
54
55    private int mWidth = 0;
56    private int mHeight = 0;
57
58    private Program mProgram;
59    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
60
61    private final String mRedEyeShader =
62            "precision mediump float;\n" +
63            "uniform sampler2D tex_sampler_0;\n" +
64            "uniform sampler2D tex_sampler_1;\n" +
65            "uniform float intensity;\n" +
66            "varying vec2 v_texcoord;\n" +
67            "void main() {\n" +
68            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
69            "  vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" +
70            "  if (mask.a > 0.0) {\n" +
71            "    float green_blue = color.g + color.b;\n" +
72            "    float red_intensity = color.r / green_blue;\n" +
73            "    if (red_intensity > intensity) {\n" +
74            "      color.r = 0.5 * green_blue;\n" +
75            "    }\n" +
76            "  }\n" +
77            "  gl_FragColor = color;\n" +
78            "}\n";
79
80    public RedEyeFilter(String name) {
81        super(name);
82    }
83
84    @Override
85    public void setupPorts() {
86        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
87        addOutputBasedOnInput("image", "image");
88    }
89
90    @Override
91    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
92        return inputFormat;
93    }
94
95    public void initProgram(FilterContext context, int target) {
96        switch (target) {
97            case FrameFormat.TARGET_GPU:
98                ShaderProgram shaderProgram = new ShaderProgram(context, mRedEyeShader);
99                shaderProgram.setMaximumTileSize(mTileSize);
100                mProgram = shaderProgram;
101                mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY);
102                break;
103            default:
104                throw new RuntimeException("Filter RedEye does not support frames of " +
105                    "target " + target + "!");
106        }
107        mTarget = target;
108    }
109
110    @Override
111    public void process(FilterContext context) {
112        // Get input frame
113        Frame input = pullInput("image");
114        FrameFormat inputFormat = input.getFormat();
115
116        // Create output frame
117        Frame output = context.getFrameManager().newFrame(inputFormat);
118
119        // Create program if not created already
120        if (mProgram == null || inputFormat.getTarget() != mTarget) {
121            initProgram(context, inputFormat.getTarget());
122        }
123
124        // Check if the frame size has changed
125        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
126            mWidth = inputFormat.getWidth();
127            mHeight = inputFormat.getHeight();
128        }
129        createRedEyeFrame(context);
130
131        // Process
132        Frame[] inputs = {input, mRedEyeFrame};
133        mProgram.process(inputs, output);
134
135        // Push output
136        pushOutput("image", output);
137
138        // Release pushed frame
139        output.release();
140
141        // Release unused frame
142        mRedEyeFrame.release();
143        mRedEyeFrame = null;
144    }
145
146    @Override
147    public void fieldPortValueUpdated(String name, FilterContext context) {
148         if (mProgram != null) {
149            updateProgramParams();
150        }
151    }
152
153    private void createRedEyeFrame(FilterContext context) {
154        int bitmapWidth = mWidth / 2;
155        int bitmapHeight = mHeight / 2;
156
157        Bitmap redEyeBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
158        mCanvas.setBitmap(redEyeBitmap);
159        mPaint.setColor(Color.WHITE);
160        mRadius = Math.max(MIN_RADIUS, RADIUS_RATIO * Math.min(bitmapWidth, bitmapHeight));
161
162        for (int i = 0; i < mCenters.length; i += 2) {
163            mCanvas.drawCircle(mCenters[i] * bitmapWidth, mCenters[i + 1] * bitmapHeight,
164                               mRadius, mPaint);
165        }
166
167        FrameFormat format = ImageFormat.create(bitmapWidth, bitmapHeight,
168                                                ImageFormat.COLORSPACE_RGBA,
169                                                FrameFormat.TARGET_GPU);
170        mRedEyeFrame = context.getFrameManager().newFrame(format);
171        mRedEyeFrame.setBitmap(redEyeBitmap);
172        redEyeBitmap.recycle();
173    }
174
175    private void updateProgramParams() {
176        if ( mCenters.length % 2 == 1) {
177            throw new RuntimeException("The size of center array must be even.");
178        }
179    }
180}
181