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 17package androidx.media.filterpacks.transform; 18 19import android.graphics.Bitmap; 20import android.graphics.Canvas; 21import android.graphics.Matrix; 22import android.graphics.Paint; 23 24import androidx.media.filterfw.Filter; 25import androidx.media.filterfw.FrameImage2D; 26import androidx.media.filterfw.FrameType; 27import androidx.media.filterfw.ImageShader; 28import androidx.media.filterfw.InputPort; 29import androidx.media.filterfw.MffContext; 30import androidx.media.filterfw.OutputPort; 31import androidx.media.filterfw.Signature; 32import androidx.media.filterfw.geometry.Quad; 33 34public class CropFilter extends Filter { 35 36 private Quad mCropRect = Quad.fromRect(0f, 0f, 1f, 1f); 37 private int mOutputWidth = 0; 38 private int mOutputHeight = 0; 39 private ImageShader mShader; 40 private boolean mUseMipmaps = false; 41 private FrameImage2D mPow2Frame = null; 42 43 public CropFilter(MffContext context, String name) { 44 super(context, name); 45 } 46 47 @Override 48 public Signature getSignature() { 49 FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU); 50 FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU); 51 return new Signature() 52 .addInputPort("image", Signature.PORT_REQUIRED, imageIn) 53 .addInputPort("cropRect", Signature.PORT_REQUIRED, FrameType.single(Quad.class)) 54 .addInputPort("outputWidth", Signature.PORT_OPTIONAL, FrameType.single(int.class)) 55 .addInputPort("outputHeight", Signature.PORT_OPTIONAL, FrameType.single(int.class)) 56 .addInputPort("useMipmaps", Signature.PORT_OPTIONAL, FrameType.single(boolean.class)) 57 .addOutputPort("image", Signature.PORT_REQUIRED, imageOut) 58 .disallowOtherPorts(); 59 } 60 61 @Override 62 public void onInputPortOpen(InputPort port) { 63 if (port.getName().equals("cropRect")) { 64 port.bindToFieldNamed("mCropRect"); 65 port.setAutoPullEnabled(true); 66 } else if (port.getName().equals("outputWidth")) { 67 port.bindToFieldNamed("mOutputWidth"); 68 port.setAutoPullEnabled(true); 69 } else if (port.getName().equals("outputHeight")) { 70 port.bindToFieldNamed("mOutputHeight"); 71 port.setAutoPullEnabled(true); 72 } else if (port.getName().equals("useMipmaps")) { 73 port.bindToFieldNamed("mUseMipmaps"); 74 port.setAutoPullEnabled(true); 75 } 76 } 77 78 @Override 79 protected void onPrepare() { 80 if (isOpenGLSupported()) { 81 mShader = ImageShader.createIdentity(); 82 } 83 } 84 85 @Override 86 protected void onProcess() { 87 OutputPort outPort = getConnectedOutputPort("image"); 88 89 // Pull input frame 90 FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D(); 91 int[] inDims = inputImage.getDimensions(); 92 int[] croppedDims = { (int)Math.ceil(mCropRect.xEdge().length() * inDims[0]), 93 (int)Math.ceil(mCropRect.yEdge().length() * inDims[1]) }; 94 int[] outDims = { getOutputWidth(croppedDims[0], croppedDims[1]), 95 getOutputHeight(croppedDims[0], croppedDims[1]) }; 96 FrameImage2D outputImage = outPort.fetchAvailableFrame(outDims).asFrameImage2D(); 97 98 if (isOpenGLSupported()) { 99 FrameImage2D sourceFrame; 100 Quad sourceQuad = null; 101 boolean scaleDown = (outDims[0] < croppedDims[0]) || (outDims[1] < croppedDims[1]); 102 if (scaleDown && mUseMipmaps) { 103 mPow2Frame = TransformUtils.makeMipMappedFrame(mPow2Frame, croppedDims); 104 int[] extDims = mPow2Frame.getDimensions(); 105 float targetWidth = croppedDims[0] / (float)extDims[0]; 106 float targetHeight = croppedDims[1] / (float)extDims[1]; 107 Quad targetQuad = Quad.fromRect(0f, 0f, targetWidth, targetHeight); 108 mShader.setSourceQuad(mCropRect); 109 mShader.setTargetQuad(targetQuad); 110 mShader.process(inputImage, mPow2Frame); 111 TransformUtils.generateMipMaps(mPow2Frame); 112 sourceFrame = mPow2Frame; 113 sourceQuad = targetQuad; 114 } else { 115 sourceFrame = inputImage; 116 sourceQuad = mCropRect; 117 } 118 119 mShader.setSourceQuad(sourceQuad); 120 mShader.setTargetRect(0f, 0f, 1f, 1f); 121 mShader.process(sourceFrame, outputImage); 122 } else { 123 // Convert quads to canvas coordinate space 124 Quad sourceQuad = mCropRect.scale2(inDims[0], inDims[1]); 125 Quad targetQuad = Quad.fromRect(0f, 0f, inDims[0], inDims[1]); 126 127 // Calculate transform for crop 128 Matrix transform = Quad.getTransform(sourceQuad, targetQuad); 129 transform.postScale(outDims[0] / (float)inDims[0], outDims[1] / (float)inDims[1]); 130 131 // Create target canvas 132 Bitmap.Config config = Bitmap.Config.ARGB_8888; 133 Bitmap cropped = Bitmap.createBitmap(outDims[0], outDims[1], config); 134 Canvas canvas = new Canvas(cropped); 135 136 // Draw source bitmap into target canvas 137 Paint paint = new Paint(); 138 paint.setFilterBitmap(true); 139 Bitmap sourceBitmap = inputImage.toBitmap(); 140 canvas.drawBitmap(sourceBitmap, transform, paint); 141 142 // Assign bitmap to output frame 143 outputImage.setBitmap(cropped); 144 } 145 146 outPort.pushFrame(outputImage); 147 } 148 149 @Override 150 protected void onClose() { 151 if (mPow2Frame != null){ 152 mPow2Frame.release(); 153 mPow2Frame = null; 154 } 155 } 156 157 protected int getOutputWidth(int inWidth, int inHeight) { 158 return mOutputWidth <= 0 ? inWidth : mOutputWidth; 159 } 160 161 protected int getOutputHeight(int inWidth, int inHeight) { 162 return mOutputHeight <= 0 ? inHeight : mOutputHeight; 163 } 164} 165