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.TargetApi;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.SurfaceTexture;
  import android.hardware.Camera;
  import android.hardware.Camera.PreviewCallback;
  import android.hardware.Camera.Size;
  import android.opengl.GLES20;
  import android.opengl.GLSurfaceView.Renderer;
  
  import jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil;
  
  import javax.microedition.khronos.egl.EGLConfig;
  import javax.microedition.khronos.opengles.GL10;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  import java.nio.IntBuffer;
  import java.util.LinkedList;
  import java.util.Queue;
  
  import static jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil.TEXTURE_NO_ROTATION;
  
  @TargetApi(11)
  public class GPUImageRenderer implements Renderer, PreviewCallback {
      public static final int NO_IMAGE = -1;
      static final float CUBE[] = {
              -1.0f, -1.0f,
              1.0f, -1.0f,
              -1.0f, 1.0f,
              1.0f, 1.0f,
      };
  
      private GPUImageFilter mFilter;
  
      public final Object mSurfaceChangedWaiter = new Object();
  
      private int mGLTextureId = NO_IMAGE;
      private SurfaceTexture mSurfaceTexture = null;
      private final FloatBuffer mGLCubeBuffer;
      private final FloatBuffer mGLTextureBuffer;
      private IntBuffer mGLRgbBuffer;
  
      private int mOutputWidth;
      private int mOutputHeight;
      private int mImageWidth;
      private int mImageHeight;
      private int mAddedPadding;
  
      private final Queue<Runnable> mRunOnDraw;
      private final Queue<Runnable> mRunOnDrawEnd;
      private Rotation mRotation;
      private boolean mFlipHorizontal;
      private boolean mFlipVertical;
      private GPUImage.ScaleType mScaleType = GPUImage.ScaleType.CENTER_CROP;
  
      private float mBackgroundRed = 0;
      private float mBackgroundGreen = 0;
      private float mBackgroundBlue = 0;
  
      public GPUImageRenderer(final GPUImageFilter filter) {
          mFilter = filter;
          mRunOnDraw = new LinkedList<Runnable>();
          mRunOnDrawEnd = new LinkedList<Runnable>();
  
          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();
          setRotation(Rotation.NORMAL, false, false);
      }
  
      @Override
      public void onSurfaceCreated(final GL10 unused, final EGLConfig config) {
          GLES20.glClearColor(mBackgroundRed, mBackgroundGreen, mBackgroundBlue, 1);
          GLES20.glDisable(GLES20.GL_DEPTH_TEST);
          mFilter.init();
      }
  
      @Override
      public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
          mOutputWidth = width;
          mOutputHeight = height;
          GLES20.glViewport(0, 0, width, height);
          GLES20.glUseProgram(mFilter.getProgram());
          mFilter.onOutputSizeChanged(width, height);
          adjustImageScaling();
          synchronized (mSurfaceChangedWaiter) {
              mSurfaceChangedWaiter.notifyAll();
          }
      }
  
      @Override
      public void onDrawFrame(final GL10 gl) {
          GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
          runAll(mRunOnDraw);
          mFilter.onDraw(mGLTextureId, mGLCubeBuffer, mGLTextureBuffer);
          runAll(mRunOnDrawEnd);
          if (mSurfaceTexture != null) {
              mSurfaceTexture.updateTexImage();
          }
      }
  
      /**
       * Sets the background color
       *
       * @param red red color value
       * @param green green color value
       * @param blue red color value
       */
      public void setBackgroundColor(float red, float green, float blue) {
          mBackgroundRed = red;
          mBackgroundGreen = green;
          mBackgroundBlue = blue;
      }
  
      private void runAll(Queue<Runnable> queue) {
          synchronized (queue) {
              while (!queue.isEmpty()) {
                  queue.poll().run();
              }
          }
      }
  
      @Override
      public void onPreviewFrame(final byte[] data, final Camera camera) {
          final Size previewSize = camera.getParameters().getPreviewSize();
          if (mGLRgbBuffer == null) {
              mGLRgbBuffer = IntBuffer.allocate(previewSize.width * previewSize.height);
          }
          if (mRunOnDraw.isEmpty()) {
              runOnDraw(new Runnable() {
                  @Override
                  public void run() {
                      GPUImageNativeLibrary.YUVtoRBGA(data, previewSize.width, previewSize.height,
                              mGLRgbBuffer.array());
                      mGLTextureId = OpenGlUtils.loadTexture(mGLRgbBuffer, previewSize, mGLTextureId);
                      camera.addCallbackBuffer(data);
  
                      if (mImageWidth != previewSize.width) {
                          mImageWidth = previewSize.width;
                          mImageHeight = previewSize.height;
                          adjustImageScaling();
                      }
                  }
              });
          }
      }
  
      public void setUpSurfaceTexture(final Camera camera) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  int[] textures = new int[1];
                  GLES20.glGenTextures(1, textures, 0);
                  mSurfaceTexture = new SurfaceTexture(textures[0]);
                  try {
                      camera.setPreviewTexture(mSurfaceTexture);
                      camera.setPreviewCallback(GPUImageRenderer.this);
                      camera.startPreview();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          });
      }
  
      public void setFilter(final GPUImageFilter filter) {
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  final GPUImageFilter oldFilter = mFilter;
                  mFilter = filter;
                  if (oldFilter != null) {
                      oldFilter.destroy();
                  }
                  mFilter.init();
                  GLES20.glUseProgram(mFilter.getProgram());
                  mFilter.onOutputSizeChanged(mOutputWidth, mOutputHeight);
              }
          });
      }
  
      public void deleteImage() {
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  GLES20.glDeleteTextures(1, new int[]{
                          mGLTextureId
                  }, 0);
                  mGLTextureId = NO_IMAGE;
              }
          });
      }
  
      public void setImageBitmap(final Bitmap bitmap) {
          setImageBitmap(bitmap, true);
      }
  
      public void setImageBitmap(final Bitmap bitmap, final boolean recycle) {
          if (bitmap == null) {
              return;
          }
  
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  Bitmap resizedBitmap = null;
                  if (bitmap.getWidth() % 2 == 1) {
                      resizedBitmap = Bitmap.createBitmap(bitmap.getWidth() + 1, bitmap.getHeight(),
                              Bitmap.Config.ARGB_8888);
                      Canvas can = new Canvas(resizedBitmap);
                      can.drawARGB(0x00, 0x00, 0x00, 0x00);
                      can.drawBitmap(bitmap, 0, 0, null);
                      mAddedPadding = 1;
                  } else {
                      mAddedPadding = 0;
                  }
  
                  mGLTextureId = OpenGlUtils.loadTexture(
                          resizedBitmap != null ? resizedBitmap : bitmap, mGLTextureId, recycle);
                  if (resizedBitmap != null) {
                      resizedBitmap.recycle();
                  }
                  mImageWidth = bitmap.getWidth();
                  mImageHeight = bitmap.getHeight();
                  adjustImageScaling();
              }
          });
      }
  
      public void setScaleType(GPUImage.ScaleType scaleType) {
          mScaleType = scaleType;
      }
  
      protected int getFrameWidth() {
          return mOutputWidth;
      }
  
      protected int getFrameHeight() {
          return mOutputHeight;
      }
  
      private void adjustImageScaling() {
          float outputWidth = mOutputWidth;
          float outputHeight = mOutputHeight;
          if (mRotation == Rotation.ROTATION_270 || mRotation == Rotation.ROTATION_90) {
              outputWidth = mOutputHeight;
              outputHeight = mOutputWidth;
          }
  
          float ratio1 = outputWidth / mImageWidth;
          float ratio2 = outputHeight / mImageHeight;
          float ratioMax = Math.max(ratio1, ratio2);
          int imageWidthNew = Math.round(mImageWidth * ratioMax);
          int imageHeightNew = Math.round(mImageHeight * ratioMax);
  
          float ratioWidth = imageWidthNew / outputWidth;
          float ratioHeight = imageHeightNew / outputHeight;
  
          float[] cube = CUBE;
          float[] textureCords = TextureRotationUtil.getRotation(mRotation, mFlipHorizontal, mFlipVertical);
          if (mScaleType == GPUImage.ScaleType.CENTER_CROP) {
              float distHorizontal = (1 - 1 / ratioWidth) / 2;
              float distVertical = (1 - 1 / ratioHeight) / 2;
              textureCords = new float[]{
                      addDistance(textureCords[0], distHorizontal), addDistance(textureCords[1], distVertical),
                      addDistance(textureCords[2], distHorizontal), addDistance(textureCords[3], distVertical),
                      addDistance(textureCords[4], distHorizontal), addDistance(textureCords[5], distVertical),
                      addDistance(textureCords[6], distHorizontal), addDistance(textureCords[7], distVertical),
              };
          } else {
              cube = new float[]{
                      CUBE[0] / ratioHeight, CUBE[1] / ratioWidth,
                      CUBE[2] / ratioHeight, CUBE[3] / ratioWidth,
                      CUBE[4] / ratioHeight, CUBE[5] / ratioWidth,
                      CUBE[6] / ratioHeight, CUBE[7] / ratioWidth,
              };
          }
  
          mGLCubeBuffer.clear();
          mGLCubeBuffer.put(cube).position(0);
          mGLTextureBuffer.clear();
          mGLTextureBuffer.put(textureCords).position(0);
      }
  
      private float addDistance(float coordinate, float distance) {
          return coordinate == 0.0f ? distance : 1 - distance;
      }
  
      public void setRotationCamera(final Rotation rotation, final boolean flipHorizontal,
              final boolean flipVertical) {
          setRotation(rotation, flipVertical, flipHorizontal);
      }
  
      public void setRotation(final Rotation rotation) {
          mRotation = rotation;
          adjustImageScaling();
      }
  
      public void setRotation(final Rotation rotation,
                              final boolean flipHorizontal, final boolean flipVertical) {
          mFlipHorizontal = flipHorizontal;
          mFlipVertical = flipVertical;
          setRotation(rotation);
      }
  
      public Rotation getRotation() {
          return mRotation;
      }
  
      public boolean isFlippedHorizontally() {
          return mFlipHorizontal;
      }
  
      public boolean isFlippedVertically() {
          return mFlipVertical;
      }
  
      protected void runOnDraw(final Runnable runnable) {
          synchronized (mRunOnDraw) {
              mRunOnDraw.add(runnable);
          }
      }
  
      protected void runOnDrawEnd(final Runnable runnable) {
          synchronized (mRunOnDrawEnd) {
              mRunOnDrawEnd.add(runnable);
          }
      }
  }
  