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