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
18package android.filterpacks.ui;
19
20import android.filterfw.core.Filter;
21import android.filterfw.core.FilterContext;
22import android.filterfw.core.Frame;
23import android.filterfw.core.FrameFormat;
24import android.filterfw.core.GenerateFieldPort;
25import android.filterfw.core.GenerateFinalPort;
26import android.filterfw.core.GLEnvironment;
27import android.filterfw.core.GLFrame;
28import android.filterfw.core.KeyValueMap;
29import android.filterfw.core.MutableFrameFormat;
30import android.filterfw.core.NativeProgram;
31import android.filterfw.core.NativeFrame;
32import android.filterfw.core.Program;
33import android.filterfw.core.ShaderProgram;
34import android.filterfw.format.ImageFormat;
35
36import android.view.Surface;
37import android.view.SurfaceHolder;
38import android.view.SurfaceView;
39
40import android.graphics.Rect;
41
42import android.util.Log;
43
44/**
45 * @hide
46 */
47public class SurfaceTargetFilter extends Filter {
48
49    private final int RENDERMODE_STRETCH   = 0;
50    private final int RENDERMODE_FIT       = 1;
51    private final int RENDERMODE_FILL_CROP = 2;
52
53    /** Required. Sets the destination surface for this node. This assumes that
54     * higher-level code is ensuring that the surface is valid, and properly
55     * updates Surface parameters if they change.
56     */
57    @GenerateFinalPort(name = "surface")
58    private Surface mSurface;
59
60    /** Required. Width of the output surface */
61    @GenerateFieldPort(name = "owidth")
62    private int mScreenWidth;
63
64    /** Required. Height of the output surface */
65    @GenerateFieldPort(name = "oheight")
66    private int mScreenHeight;
67
68    /** Optional. Control how the incoming frames are rendered onto the
69     * output. Default is FIT.
70     * RENDERMODE_STRETCH: Just fill the output surfaceView.
71     * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May
72     * have black bars.
73     * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black
74     * bars. May crop.
75     */
76    @GenerateFieldPort(name = "renderMode", hasDefault = true)
77    private String mRenderModeString;
78
79    private ShaderProgram mProgram;
80    private GLEnvironment mGlEnv;
81    private GLFrame mScreen;
82    private int mRenderMode = RENDERMODE_FIT;
83    private float mAspectRatio = 1.f;
84
85    private int mSurfaceId = -1;
86
87    private boolean mLogVerbose;
88    private static final String TAG = "SurfaceRenderFilter";
89
90    public SurfaceTargetFilter(String name) {
91        super(name);
92
93        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
94    }
95
96    @Override
97    public void setupPorts() {
98        // Make sure we have a Surface
99        if (mSurface == null) {
100            throw new RuntimeException("NULL Surface passed to SurfaceTargetFilter");
101        }
102
103        // Add input port
104        addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
105    }
106
107    public void updateRenderMode() {
108        if (mRenderModeString != null) {
109            if (mRenderModeString.equals("stretch")) {
110                mRenderMode = RENDERMODE_STRETCH;
111            } else if (mRenderModeString.equals("fit")) {
112                mRenderMode = RENDERMODE_FIT;
113            } else if (mRenderModeString.equals("fill_crop")) {
114                mRenderMode = RENDERMODE_FILL_CROP;
115            } else {
116                throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!");
117            }
118        }
119        updateTargetRect();
120    }
121
122    @Override
123    public void prepare(FilterContext context) {
124        mGlEnv = context.getGLEnvironment();
125
126        // Create identity shader to render, and make sure to render upside-down, as textures
127        // are stored internally bottom-to-top.
128        mProgram = ShaderProgram.createIdentity(context);
129        mProgram.setSourceRect(0, 1, 1, -1);
130        mProgram.setClearsOutput(true);
131        mProgram.setClearColor(0.0f, 0.0f, 0.0f);
132
133        MutableFrameFormat screenFormat = ImageFormat.create(mScreenWidth,
134                                                             mScreenHeight,
135                                                             ImageFormat.COLORSPACE_RGBA,
136                                                             FrameFormat.TARGET_GPU);
137        mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat,
138                                                                   GLFrame.EXISTING_FBO_BINDING,
139                                                                   0);
140
141        // Set up cropping
142        updateRenderMode();
143    }
144
145    @Override
146    public void open(FilterContext context) {
147        registerSurface();
148    }
149
150    @Override
151    public void process(FilterContext context) {
152        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
153
154        // Get input frame
155        Frame input = pullInput("frame");
156        boolean createdFrame = false;
157
158        float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight();
159        if (currentAspectRatio != mAspectRatio) {
160            if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio);
161            mAspectRatio = currentAspectRatio;
162            updateTargetRect();
163        }
164
165        // See if we need to copy to GPU
166        Frame gpuFrame = null;
167        if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat());
168        int target = input.getFormat().getTarget();
169        if (target != FrameFormat.TARGET_GPU) {
170            gpuFrame = context.getFrameManager().duplicateFrameToTarget(input,
171                                                                        FrameFormat.TARGET_GPU);
172            createdFrame = true;
173        } else {
174            gpuFrame = input;
175        }
176
177        // Activate our surface
178        mGlEnv.activateSurfaceWithId(mSurfaceId);
179
180        // Process
181        mProgram.process(gpuFrame, mScreen);
182
183        // And swap buffers
184        mGlEnv.swapBuffers();
185
186        if (createdFrame) {
187            gpuFrame.release();
188        }
189    }
190
191    @Override
192    public void fieldPortValueUpdated(String name, FilterContext context) {
193        mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight);
194        updateTargetRect();
195    }
196
197    @Override
198    public void close(FilterContext context) {
199        unregisterSurface();
200    }
201
202    @Override
203    public void tearDown(FilterContext context) {
204        if (mScreen != null) {
205            mScreen.release();
206        }
207    }
208
209    private void updateTargetRect() {
210        if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
211            float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
212            float relativeAspectRatio = screenAspectRatio / mAspectRatio;
213
214            switch (mRenderMode) {
215                case RENDERMODE_STRETCH:
216                    mProgram.setTargetRect(0, 0, 1, 1);
217                    break;
218                case RENDERMODE_FIT:
219                    if (relativeAspectRatio > 1.0f) {
220                        // Screen is wider than the camera, scale down X
221                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
222                                               1.0f / relativeAspectRatio, 1.0f);
223                    } else {
224                        // Screen is taller than the camera, scale down Y
225                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
226                                               1.0f, relativeAspectRatio);
227                    }
228                    break;
229                case RENDERMODE_FILL_CROP:
230                    if (relativeAspectRatio > 1) {
231                        // Screen is wider than the camera, crop in Y
232                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
233                                               1.0f, relativeAspectRatio);
234                    } else {
235                        // Screen is taller than the camera, crop in X
236                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
237                                               1.0f / relativeAspectRatio, 1.0f);
238                    }
239                    break;
240            }
241        }
242    }
243
244    private void registerSurface() {
245        mSurfaceId = mGlEnv.registerSurface(mSurface);
246        if (mSurfaceId < 0) {
247            throw new RuntimeException("Could not register Surface: " + mSurface);
248        }
249    }
250
251    private void unregisterSurface() {
252        if (mSurfaceId > 0) {
253            mGlEnv.unregisterSurfaceId(mSurfaceId);
254        }
255    }
256
257}
258