content: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  package android.filterpacks.ui;
  
  import android.filterfw.core.Filter;
  import android.filterfw.core.FilterContext;
  import android.filterfw.core.Frame;
  import android.filterfw.core.FrameFormat;
  import android.filterfw.core.GenerateFieldPort;
  import android.filterfw.core.GenerateFinalPort;
  import android.filterfw.core.GLEnvironment;
  import android.filterfw.core.GLFrame;
  import android.filterfw.core.MutableFrameFormat;
  import android.filterfw.core.ShaderProgram;
  import android.filterfw.format.ImageFormat;
  
  import android.view.Surface;
  
  import android.util.Log;
  
  /**
   * @hide
   */
  public class SurfaceTargetFilter extends Filter {
  
      private final int RENDERMODE_STRETCH   = 0;
      private final int RENDERMODE_FIT       = 1;
      private final int RENDERMODE_FILL_CROP = 2;
  
      /** Required. Sets the destination surface for this node. This assumes that
       * higher-level code is ensuring that the surface is valid, and properly
       * updates Surface parameters if they change.
       */
      @GenerateFinalPort(name = "surface")
      private Surface mSurface;
  
      /** Required. Width of the output surface */
      @GenerateFieldPort(name = "owidth")
      private int mScreenWidth;
  
      /** Required. Height of the output surface */
      @GenerateFieldPort(name = "oheight")
      private int mScreenHeight;
  
      /** Optional. Control how the incoming frames are rendered onto the
       * output. Default is FIT.
       * RENDERMODE_STRETCH: Just fill the output surfaceView.
       * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May
       * have black bars.
       * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black
       * bars. May crop.
       */
      @GenerateFieldPort(name = "renderMode", hasDefault = true)
      private String mRenderModeString;
  
      private ShaderProgram mProgram;
      private GLEnvironment mGlEnv;
      private GLFrame mScreen;
      private int mRenderMode = RENDERMODE_FIT;
      private float mAspectRatio = 1.f;
  
      private int mSurfaceId = -1;
  
      private boolean mLogVerbose;
      private static final String TAG = "SurfaceRenderFilter";
  
      public SurfaceTargetFilter(String name) {
          super(name);
  
          mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
      }
  
      @Override
      public void setupPorts() {
          // Make sure we have a Surface
          if (mSurface == null) {
              throw new RuntimeException("NULL Surface passed to SurfaceTargetFilter");
          }
  
          // Add input port
          addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
      }
  
      public void updateRenderMode() {
          if (mRenderModeString != null) {
              if (mRenderModeString.equals("stretch")) {
                  mRenderMode = RENDERMODE_STRETCH;
              } else if (mRenderModeString.equals("fit")) {
                  mRenderMode = RENDERMODE_FIT;
              } else if (mRenderModeString.equals("fill_crop")) {
                  mRenderMode = RENDERMODE_FILL_CROP;
              } else {
                  throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!");
              }
          }
          updateTargetRect();
      }
  
      @Override
      public void prepare(FilterContext context) {
          mGlEnv = context.getGLEnvironment();
  
          // Create identity shader to render, and make sure to render upside-down, as textures
          // are stored internally bottom-to-top.
          mProgram = ShaderProgram.createIdentity(context);
          mProgram.setSourceRect(0, 1, 1, -1);
          mProgram.setClearsOutput(true);
          mProgram.setClearColor(0.0f, 0.0f, 0.0f);
  
          MutableFrameFormat screenFormat = ImageFormat.create(mScreenWidth,
                                                               mScreenHeight,
                                                               ImageFormat.COLORSPACE_RGBA,
                                                               FrameFormat.TARGET_GPU);
          mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat,
                                                                     GLFrame.EXISTING_FBO_BINDING,
                                                                     0);
  
          // Set up cropping
          updateRenderMode();
      }
  
      @Override
      public void open(FilterContext context) {
          registerSurface();
      }
  
      @Override
      public void process(FilterContext context) {
          if (mLogVerbose) Log.v(TAG, "Starting frame processing");
  
          // Get input frame
          Frame input = pullInput("frame");
          boolean createdFrame = false;
  
          float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight();
          if (currentAspectRatio != mAspectRatio) {
              if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio);
              mAspectRatio = currentAspectRatio;
              updateTargetRect();
          }
  
          // See if we need to copy to GPU
          Frame gpuFrame = null;
          if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat());
          int target = input.getFormat().getTarget();
          if (target != FrameFormat.TARGET_GPU) {
              gpuFrame = context.getFrameManager().duplicateFrameToTarget(input,
                                                                          FrameFormat.TARGET_GPU);
              createdFrame = true;
          } else {
              gpuFrame = input;
          }
  
          // Activate our surface
          mGlEnv.activateSurfaceWithId(mSurfaceId);
  
          // Process
          mProgram.process(gpuFrame, mScreen);
  
          // And swap buffers
          mGlEnv.swapBuffers();
  
          if (createdFrame) {
              gpuFrame.release();
          }
      }
  
      @Override
      public void fieldPortValueUpdated(String name, FilterContext context) {
          mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight);
          updateTargetRect();
      }
  
      @Override
      public void close(FilterContext context) {
          unregisterSurface();
      }
  
      @Override
      public void tearDown(FilterContext context) {
          if (mScreen != null) {
              mScreen.release();
          }
      }
  
      private void updateTargetRect() {
          if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
              float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
              float relativeAspectRatio = screenAspectRatio / mAspectRatio;
  
              switch (mRenderMode) {
                  case RENDERMODE_STRETCH:
                      mProgram.setTargetRect(0, 0, 1, 1);
                      break;
                  case RENDERMODE_FIT:
                      if (relativeAspectRatio > 1.0f) {
                          // Screen is wider than the camera, scale down X
                          mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
                                                 1.0f / relativeAspectRatio, 1.0f);
                      } else {
                          // Screen is taller than the camera, scale down Y
                          mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
                                                 1.0f, relativeAspectRatio);
                      }
                      break;
                  case RENDERMODE_FILL_CROP:
                      if (relativeAspectRatio > 1) {
                          // Screen is wider than the camera, crop in Y
                          mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
                                                 1.0f, relativeAspectRatio);
                      } else {
                          // Screen is taller than the camera, crop in X
                          mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
                                                 1.0f / relativeAspectRatio, 1.0f);
                      }
                      break;
              }
          }
      }
  
      private void registerSurface() {
          mSurfaceId = mGlEnv.registerSurface(mSurface);
          if (mSurfaceId < 0) {
              throw new RuntimeException("Could not register Surface: " + mSurface);
          }
      }
  
      private void unregisterSurface() {
          if (mSurfaceId > 0) {
              mGlEnv.unregisterSurfaceId(mSurfaceId);
          }
      }
  
  }
  