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.MutableFrameFormat; 29import android.filterfw.core.ShaderProgram; 30import android.filterfw.format.ImageFormat; 31 32import android.view.Surface; 33 34import android.util.Log; 35 36/** 37 * @hide 38 */ 39public 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