content: /*
   * Copyright (C) 2012 CyberAgent
   *
   * 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 jp.co.cyberagent.android.gpuimage;
  
  import android.annotation.SuppressLint;
  import android.opengl.GLES20;
  import jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  import java.util.ArrayList;
  import java.util.List;
  
  import static jp.co.cyberagent.android.gpuimage.GPUImageRenderer.CUBE;
  import static jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil.TEXTURE_NO_ROTATION;
  
  /**
   * Resembles a filter that consists of multiple filters applied after each
   * other.
   */
  public class GPUImageFilterGroup extends GPUImageFilter {
  
      protected List<GPUImageFilter> mFilters;
      protected List<GPUImageFilter> mMergedFilters;
      private int[] mFrameBuffers;
      private int[] mFrameBufferTextures;
  
      private final FloatBuffer mGLCubeBuffer;
      private final FloatBuffer mGLTextureBuffer;
      private final FloatBuffer mGLTextureFlipBuffer;
  
      /**
       * Instantiates a new GPUImageFilterGroup with no filters.
       */
      public GPUImageFilterGroup() {
          this(null);
      }
  
      /**
       * Instantiates a new GPUImageFilterGroup with the given filters.
       *
       * @param filters the filters which represent this filter
       */
      public GPUImageFilterGroup(List<GPUImageFilter> filters) {
          mFilters = filters;
          if (mFilters == null) {
              mFilters = new ArrayList<GPUImageFilter>();
          } else {
              updateMergedFilters();
          }
  
          mGLCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer();
          mGLCubeBuffer.put(CUBE).position(0);
  
          mGLTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer();
          mGLTextureBuffer.put(TEXTURE_NO_ROTATION).position(0);
  
          float[] flipTexture = TextureRotationUtil.getRotation(Rotation.NORMAL, false, true);
          mGLTextureFlipBuffer = ByteBuffer.allocateDirect(flipTexture.length * 4)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer();
          mGLTextureFlipBuffer.put(flipTexture).position(0);
      }
  
      public void addFilter(GPUImageFilter aFilter) {
          if (aFilter == null) {
              return;
          }
          mFilters.add(aFilter);
          updateMergedFilters();
      }
  
      /*
       * (non-Javadoc)
       * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onInit()
       */
      @Override
      public void onInit() {
          super.onInit();
          for (GPUImageFilter filter : mFilters) {
              filter.init();
          }
      }
  
      /*
       * (non-Javadoc)
       * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onDestroy()
       */
      @Override
      public void onDestroy() {
          destroyFramebuffers();
          for (GPUImageFilter filter : mFilters) {
              filter.destroy();
          }
          super.onDestroy();
      }
  
      private void destroyFramebuffers() {
          if (mFrameBufferTextures != null) {
              GLES20.glDeleteTextures(mFrameBufferTextures.length, mFrameBufferTextures, 0);
              mFrameBufferTextures = null;
          }
          if (mFrameBuffers != null) {
              GLES20.glDeleteFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
              mFrameBuffers = null;
          }
      }
  
      /*
       * (non-Javadoc)
       * @see
       * jp.co.cyberagent.android.gpuimage.GPUImageFilter#onOutputSizeChanged(int,
       * int)
       */
      @Override
      public void onOutputSizeChanged(final int width, final int height) {
          super.onOutputSizeChanged(width, height);
          if (mFrameBuffers != null) {
              destroyFramebuffers();
          }
  
          int size = mFilters.size();
          for (int i = 0; i < size; i++) {
              mFilters.get(i).onOutputSizeChanged(width, height);
          }
  
          if (mMergedFilters != null && mMergedFilters.size() > 0) {
              size = mMergedFilters.size();
              mFrameBuffers = new int[size - 1];
              mFrameBufferTextures = new int[size - 1];
  
              for (int i = 0; i < size - 1; i++) {
                  GLES20.glGenFramebuffers(1, mFrameBuffers, i);
                  GLES20.glGenTextures(1, mFrameBufferTextures, i);
                  GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i]);
                  GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
                          GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
                  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                          GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
                  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                          GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
                  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                          GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
                  GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                          GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  
                  GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]);
                  GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                          GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i], 0);
  
                  GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
                  GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
              }
          }
      }
  
      /*
       * (non-Javadoc)
       * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onDraw(int,
       * java.nio.FloatBuffer, java.nio.FloatBuffer)
       */
      @SuppressLint("WrongCall")    
      @Override
      public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
                         final FloatBuffer textureBuffer) {
          runPendingOnDrawTasks();
          if (!isInitialized() || mFrameBuffers == null || mFrameBufferTextures == null) {
              return;
          }
          if (mMergedFilters != null) {
              int size = mMergedFilters.size();
              int previousTexture = textureId;
              for (int i = 0; i < size; i++) {
                  GPUImageFilter filter = mMergedFilters.get(i);
                  boolean isNotLast = i < size - 1;
                  if (isNotLast) {
                      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]);
                      GLES20.glClearColor(0, 0, 0, 0);
                  }
  
                  if (i == 0) {
                      filter.onDraw(previousTexture, cubeBuffer, textureBuffer);
                  } else if (i == size - 1) {
                      filter.onDraw(previousTexture, mGLCubeBuffer, (size % 2 == 0) ? mGLTextureFlipBuffer : mGLTextureBuffer);
                  } else {
                      filter.onDraw(previousTexture, mGLCubeBuffer, mGLTextureBuffer);
                  }
  
                  if (isNotLast) {
                      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
                      previousTexture = mFrameBufferTextures[i];
                  }
              }
          }
       }
  
      /**
       * Gets the filters.
       *
       * @return the filters
       */
      public List<GPUImageFilter> getFilters() {
          return mFilters;
      }
  
      public List<GPUImageFilter> getMergedFilters() {
          return mMergedFilters;
      }
  
      public void updateMergedFilters() {
          if (mFilters == null) {
              return;
          }
  
          if (mMergedFilters == null) {
              mMergedFilters = new ArrayList<GPUImageFilter>();
          } else {
              mMergedFilters.clear();
          }
  
          List<GPUImageFilter> filters;
          for (GPUImageFilter filter : mFilters) {
              if (filter instanceof GPUImageFilterGroup) {
                  ((GPUImageFilterGroup) filter).updateMergedFilters();
                  filters = ((GPUImageFilterGroup) filter).getMergedFilters();
                  if (filters == null || filters.isEmpty())
                      continue;
                  mMergedFilters.addAll(filters);
                  continue;
              }
              mMergedFilters.add(filter);
          }
      }
  }
  