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