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