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.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.KeyValueMap;
     29 import android.filterfw.core.MutableFrameFormat;
     30 import android.filterfw.core.NativeProgram;
     31 import android.filterfw.core.NativeFrame;
     32 import android.filterfw.core.Program;
     33 import android.filterfw.core.ShaderProgram;
     34 import android.filterfw.format.ImageFormat;
     35 
     36 import android.view.Surface;
     37 import android.view.SurfaceHolder;
     38 import android.view.SurfaceView;
     39 
     40 import android.graphics.Rect;
     41 
     42 import android.util.Log;
     43 
     44 /**
     45  * @hide
     46  */
     47 public 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