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.filterfw;
18
19import android.graphics.Bitmap;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.Rect;
23import android.graphics.RectF;
24import androidx.media.filterfw.BackingStore.Backing;
25
26public class FrameImage2D extends FrameBuffer2D {
27
28    /**
29     * Access frame's data using a TextureSource.
30     * This is a convenience method and is equivalent to calling {@code lockData} with an
31     * {@code accessFormat} of {@code ACCESS_TEXTURE}.
32     *
33     * @return The TextureSource instance holding the Frame's data.
34     */
35    public TextureSource lockTextureSource() {
36        return (TextureSource)mBackingStore.lockData(MODE_READ, BackingStore.ACCESS_TEXTURE);
37    }
38
39    /**
40     * Access frame's data using a RenderTarget.
41     * This is a convenience method and is equivalent to calling {@code lockData} with an
42     * {@code accessFormat} of {@code ACCESS_RENDERTARGET}.
43     *
44     * @return The RenderTarget instance holding the Frame's data.
45     */
46    public RenderTarget lockRenderTarget() {
47        return (RenderTarget)mBackingStore.lockData(MODE_WRITE, BackingStore.ACCESS_RENDERTARGET);
48    }
49
50    /**
51     * Assigns the pixel data of the specified bitmap.
52     *
53     * The RGBA pixel data will be extracted from the bitmap and assigned to the frame data. Note,
54     * that the colors are premultiplied with the alpha channel. If you wish to have
55     * non-premultiplied colors, you must pass the Frame through an
56     * {@code UnpremultiplyAlphaFilter}.
57     *
58     * @param bitmap The bitmap pixels to assign.
59     */
60    public void setBitmap(Bitmap bitmap) {
61        bitmap = convertToFrameType(bitmap, mBackingStore.getFrameType());
62        validateBitmapSize(bitmap, mBackingStore.getDimensions());
63        Backing backing = mBackingStore.lockBacking(MODE_WRITE, BackingStore.ACCESS_BITMAP);
64        backing.setData(bitmap);
65        mBackingStore.unlock();
66    }
67
68    /**
69     * Returns the RGBA image contents as a Bitmap instance.
70     *
71     * @return a Bitmap instance holding the RGBA Frame image content.
72     */
73    public Bitmap toBitmap() {
74        Bitmap result = (Bitmap)mBackingStore.lockData(MODE_READ, BackingStore.ACCESS_BITMAP);
75        mBackingStore.unlock();
76        return result;
77    }
78
79    /**
80     * Copies the image data from one frame to another.
81     *
82     * The source and target rectangles must be given in normalized coordinates, where 0,0 is the
83     * top-left of the image and 1,1 is the bottom-right.
84     *
85     * If the target rectangle is smaller than the target frame, the pixel values outside of the
86     * target rectangle are undefined.
87     *
88     * This method must be called within a Filter during execution. It supports both GL-enabled
89     * and GL-disabled run contexts.
90     *
91     * @param target The target frame to copy to.
92     * @param sourceRect The source rectangle in normalized coordinates.
93     * @param targetRect The target rectangle in normalized coordinates.
94     */
95    public void copyToFrame(FrameImage2D target, RectF sourceRect, RectF targetRect) {
96        if (GraphRunner.current().isOpenGLSupported()) {
97            gpuImageCopy(this, target, sourceRect, targetRect);
98        } else {
99            cpuImageCopy(this, target, sourceRect, targetRect);
100        }
101    }
102
103    static FrameImage2D create(BackingStore backingStore) {
104        assertCanCreate(backingStore);
105        return new FrameImage2D(backingStore);
106    }
107
108    FrameImage2D(BackingStore backingStore) {
109        super(backingStore);
110    }
111
112    static void assertCanCreate(BackingStore backingStore) {
113        FrameBuffer2D.assertCanCreate(backingStore);
114    }
115
116    private static Bitmap convertToFrameType(Bitmap bitmap, FrameType type) {
117        Bitmap.Config config = bitmap.getConfig();
118        Bitmap result = bitmap;
119        switch(type.getElementId()) {
120            case FrameType.ELEMENT_RGBA8888:
121                if (config != Bitmap.Config.ARGB_8888) {
122                    result = bitmap.copy(Bitmap.Config.ARGB_8888, false);
123                    if (result == null) {
124                        throw new RuntimeException("Could not convert bitmap to frame-type " +
125                                "RGBA8888!");
126                    }
127                }
128                break;
129            default:
130                throw new IllegalArgumentException("Unsupported frame type '" + type + "' for " +
131                        "bitmap assignment!");
132        }
133        return result;
134    }
135
136    private void validateBitmapSize(Bitmap bitmap, int[] dimensions) {
137        if (bitmap.getWidth() != dimensions[0] || bitmap.getHeight() != dimensions[1]) {
138            throw new IllegalArgumentException("Cannot assign bitmap of size " + bitmap.getWidth()
139                    + "x" + bitmap.getHeight() + " to frame of size " + dimensions[0] + "x"
140                    + dimensions[1] + "!");
141        }
142    }
143
144    private static void gpuImageCopy(
145            FrameImage2D srcImage, FrameImage2D dstImage, RectF srcRect, RectF dstRect) {
146        ImageShader idShader = RenderTarget.currentTarget().getIdentityShader();
147        // We briefly modify the shader
148        // TODO: Implement a safer way to save and restore a shared shader.
149        idShader.setSourceRect(srcRect);
150        idShader.setTargetRect(dstRect);
151        idShader.process(srcImage, dstImage);
152        // And reset it as others may use it as well
153        idShader.setSourceRect(0f, 0f, 1f, 1f);
154        idShader.setTargetRect(0f, 0f, 1f, 1f);
155    }
156
157    private static void cpuImageCopy(
158            FrameImage2D srcImage, FrameImage2D dstImage, RectF srcRect, RectF dstRect) {
159        // Convert rectangles to integer rectangles in image dimensions
160        Rect srcIRect = new Rect((int) srcRect.left * srcImage.getWidth(),
161                (int) srcRect.top * srcImage.getHeight(),
162                (int) srcRect.right * srcImage.getWidth(),
163                (int) srcRect.bottom * srcImage.getHeight());
164        Rect dstIRect = new Rect((int) dstRect.left * srcImage.getWidth(),
165                (int) dstRect.top * srcImage.getHeight(),
166                (int) dstRect.right * srcImage.getWidth(),
167                (int) dstRect.bottom * srcImage.getHeight());
168
169        // Create target canvas
170        Bitmap.Config config = Bitmap.Config.ARGB_8888;
171        Bitmap dstBitmap = Bitmap.createBitmap(dstImage.getWidth(), dstImage.getHeight(), config);
172        Canvas canvas = new Canvas(dstBitmap);
173
174        // Draw source bitmap into target canvas
175        Paint paint = new Paint();
176        paint.setFilterBitmap(true);
177        Bitmap srcBitmap = srcImage.toBitmap();
178        canvas.drawBitmap(srcBitmap, srcIRect, dstIRect, paint);
179
180        // Assign bitmap to output frame
181        dstImage.setBitmap(dstBitmap);
182    }
183}
184
185