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