1/*
2 * Copyright (C) 2010 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
17
18package android.media.videoeditor;
19
20import java.io.File;
21import java.io.FileNotFoundException;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.io.DataOutputStream;
25import java.nio.ByteBuffer;
26import java.nio.IntBuffer;
27
28import android.graphics.Canvas;
29import android.graphics.Paint;
30import android.graphics.Rect;
31import android.graphics.Bitmap;
32import android.graphics.BitmapFactory;
33import android.graphics.Bitmap.CompressFormat;
34import android.util.Pair;
35
36
37/**
38 * This class is used to overlay an image on top of a media item.
39 * {@hide}
40 */
41public class OverlayFrame extends Overlay {
42    /**
43     *  Instance variables
44     */
45    private Bitmap mBitmap;
46    private String mFilename;
47    private String mBitmapFileName;
48
49    private int mOFWidth;
50    private int mOFHeight;
51
52    /**
53     * resized RGB Image dimensions
54     */
55    private int mResizedRGBWidth;
56    private int mResizedRGBHeight;
57
58    /**
59     *  The resize paint
60     */
61    private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
62
63    /**
64     * An object of this type cannot be instantiated by using the default
65     * constructor
66     */
67    @SuppressWarnings("unused")
68    private OverlayFrame() {
69        this(null, null, (String)null, 0, 0);
70    }
71
72    /**
73     * Constructor for an OverlayFrame
74     *
75     * @param mediaItem The media item owner
76     * @param overlayId The overlay id
77     * @param bitmap The bitmap to be used as an overlay. The size of the
78     *      bitmap must equal to the size of the media item to which it is
79     *      added. The bitmap is typically a decoded PNG file.
80     * @param startTimeMs The overlay start time in milliseconds
81     * @param durationMs The overlay duration in milliseconds
82     *
83     * @throws IllegalArgumentException if the file type is not PNG or the
84     *      startTimeMs and durationMs are incorrect.
85     */
86    public OverlayFrame(MediaItem mediaItem, String overlayId, Bitmap bitmap,
87                        long startTimeMs,long durationMs) {
88        super(mediaItem, overlayId, startTimeMs, durationMs);
89        mBitmap = bitmap;
90        mFilename = null;
91        mBitmapFileName = null;
92        mResizedRGBWidth = 0;
93        mResizedRGBHeight = 0;
94    }
95
96    /**
97     * Constructor for an OverlayFrame. This constructor can be used to
98     * restore the overlay after it was saved internally by the video editor.
99     *
100     * @param mediaItem The media item owner
101     * @param overlayId The overlay id
102     * @param filename The file name that contains the overlay.
103     * @param startTimeMs The overlay start time in milliseconds
104     * @param durationMs The overlay duration in milliseconds
105     *
106     * @throws IllegalArgumentException if the file type is not PNG or the
107     *      startTimeMs and durationMs are incorrect.
108     */
109    OverlayFrame(MediaItem mediaItem, String overlayId, String filename,
110                 long startTimeMs,long durationMs) {
111        super(mediaItem, overlayId, startTimeMs, durationMs);
112        mBitmapFileName = filename;
113        mBitmap = BitmapFactory.decodeFile(mBitmapFileName);
114        mFilename = null;
115        mResizedRGBWidth = 0;
116        mResizedRGBHeight = 0;
117    }
118
119    /**
120     * Get the overlay bitmap.
121     *
122     * @return Get the overlay bitmap
123     */
124    public Bitmap getBitmap() {
125        return mBitmap;
126    }
127
128    /**
129     * Get the overlay bitmap.
130     *
131     * @return Get the overlay bitmap as png file.
132     */
133    String getBitmapImageFileName() {
134        return mBitmapFileName;
135    }
136    /**
137     * Set the overlay bitmap.
138     *
139     * @param bitmap The overlay bitmap.
140     */
141    public void setBitmap(Bitmap bitmap) {
142        getMediaItem().getNativeContext().setGeneratePreview(true);
143
144        invalidate();
145
146        mBitmap = bitmap;
147        if (mFilename != null) {
148            /**
149             *  Delete the file
150             */
151            new File(mFilename).delete();
152            /**
153             *  Invalidate the filename
154             */
155            mFilename = null;
156        }
157
158        /**
159         *  Invalidate the transitions if necessary
160         */
161        getMediaItem().invalidateTransitions(mStartTimeMs, mDurationMs);
162    }
163
164    /**
165     * Get the file name of this overlay
166     */
167    String getFilename() {
168        return mFilename;
169    }
170
171    /*
172     * Set the file name of this overlay
173     */
174    void setFilename(String filename) {
175        mFilename = filename;
176    }
177    /**
178     * Save the overlay to the project folder
179     *
180     * @param path The path where the overlay will be saved
181     *
182     * @return The filename
183     * @throws FileNotFoundException if the bitmap cannot be saved
184     * @throws IOException if the bitmap file cannot be saved
185     */
186    String save(String path) throws FileNotFoundException, IOException {
187        if (mFilename != null) {
188            return mFilename;
189        }
190
191        // Create the compressed PNG file
192        mBitmapFileName = path + "/" + "Overlay" + getId() + ".png";
193        if (!(new File(mBitmapFileName).exists())) {
194            final FileOutputStream out = new FileOutputStream (mBitmapFileName);
195            mBitmap.compress(CompressFormat.PNG, 100, out);
196            out.flush();
197            out.close();
198        }
199
200        mOFWidth = mBitmap.getWidth();
201        mOFHeight = mBitmap.getHeight();
202
203        mFilename = path + "/" + "Overlay" + getId() + ".rgb";
204
205        /* resize and save rgb as per project aspect ratio */
206        MediaArtistNativeHelper nativeHelper = (super.getMediaItem()).getNativeContext();
207
208        /* get height and width for story board aspect ratio */
209        final Pair<Integer, Integer> maxResolution;
210        final Pair<Integer, Integer>[] resolutions;
211        resolutions = MediaProperties.getSupportedResolutions(nativeHelper.nativeHelperGetAspectRatio());
212
213        // Get the highest resolution
214        maxResolution = resolutions[resolutions.length - 1];
215
216        /* Generate the rgb file with rendering mode */
217        generateOverlayWithRenderingMode (super.getMediaItem(), this,
218                maxResolution.second /* max Height */ ,
219                maxResolution.first /* max Width */);
220
221        return mFilename;
222    }
223
224    /**
225     * Get the OverlayFrame Height
226     */
227     int getOverlayFrameHeight() {
228         return mOFHeight;
229     }
230
231     /**
232     * Get the OverlayFrame Width
233     */
234     int getOverlayFrameWidth() {
235         return mOFWidth;
236     }
237
238    /*
239     * Set the OverlayFrame Height
240     */
241     void setOverlayFrameHeight(int height) {
242         mOFHeight = height;
243     }
244
245    /*
246     * Set the OverlayFrame Width
247     */
248     void setOverlayFrameWidth(int width) {
249         mOFWidth = width;
250     }
251
252    /*
253     * Set the resized RGB widht and height
254     */
255     void setResizedRGBSize(int width, int height) {
256        mResizedRGBWidth = width;
257        mResizedRGBHeight = height;
258     }
259
260    /*
261     * Get the resized RGB Height
262     */
263     int getResizedRGBSizeHeight() {
264         return mResizedRGBHeight;
265     }
266
267    /*
268     * Get the resized RGB Width
269     */
270     int getResizedRGBSizeWidth() {
271         return mResizedRGBWidth;
272     }
273
274
275    /**
276     * Delete the overlay files
277     */
278    void invalidate() {
279        if (mBitmap != null) {
280            mBitmap.recycle();
281            mBitmap = null;
282        }
283
284        if (mFilename != null) {
285            new File(mFilename).delete();
286            mFilename = null;
287        }
288
289        if (mBitmapFileName != null) {
290            new File(mBitmapFileName).delete();
291            mBitmapFileName = null;
292        }
293    }
294
295     /**
296     * Delete the overlay related files
297     */
298    void invalidateGeneratedFiles() {
299        if (mFilename != null) {
300            new File(mFilename).delete();
301            mFilename = null;
302        }
303
304        if (mBitmapFileName != null) {
305            new File(mBitmapFileName).delete();
306            mBitmapFileName = null;
307        }
308    }
309
310    void generateOverlayWithRenderingMode (MediaItem mediaItemsList, OverlayFrame overlay, int height , int width)
311        throws FileNotFoundException, IOException {
312
313        final MediaItem t = mediaItemsList;
314
315        /* get the rendering mode */
316        int renderMode = t.getRenderingMode();
317
318        Bitmap overlayBitmap = ((OverlayFrame)overlay).getBitmap();
319
320        /*
321         * Check if the resize of Overlay is needed with rendering mode applied
322         * because of change in export dimensions
323         */
324        int resizedRGBFileHeight = ((OverlayFrame)overlay).getResizedRGBSizeHeight();
325        int resizedRGBFileWidth = ((OverlayFrame)overlay).getResizedRGBSizeWidth();
326
327        /* Get original bitmap width if it is not resized */
328        if(resizedRGBFileWidth == 0) {
329            resizedRGBFileWidth = overlayBitmap.getWidth();
330        }
331        /* Get original bitmap height if it is not resized */
332        if(resizedRGBFileHeight == 0) {
333            resizedRGBFileHeight = overlayBitmap.getHeight();
334        }
335
336        if (resizedRGBFileWidth != width || resizedRGBFileHeight != height
337            || (!(new File(((OverlayFrame)overlay).getFilename()).exists()))) {
338            /*
339             *  Create the canvas bitmap
340             */
341            final Bitmap destBitmap = Bitmap.createBitmap((int)width,
342                                                      (int)height,
343                                                      Bitmap.Config.ARGB_8888);
344            final Canvas overlayCanvas = new Canvas(destBitmap);
345            final Rect destRect;
346            final Rect srcRect;
347
348            switch (renderMode) {
349                case MediaItem.RENDERING_MODE_STRETCH: {
350                    destRect = new Rect(0, 0, overlayCanvas.getWidth(),
351                                             overlayCanvas.getHeight());
352                    srcRect = new Rect(0, 0, overlayBitmap.getWidth(),
353                                             overlayBitmap.getHeight());
354                    break;
355                }
356
357                case MediaItem.RENDERING_MODE_BLACK_BORDER: {
358                    int left, right, top, bottom;
359                    float aROverlayImage, aRCanvas;
360                    aROverlayImage = (float)(overlayBitmap.getWidth()) /
361                                     (float)(overlayBitmap.getHeight());
362
363                    aRCanvas = (float)(overlayCanvas.getWidth()) /
364                                     (float)(overlayCanvas.getHeight());
365
366                    if (aROverlayImage > aRCanvas) {
367                        int newHeight = ((overlayCanvas.getWidth() * overlayBitmap.getHeight())
368                                         / overlayBitmap.getWidth());
369                        left = 0;
370                        top  = (overlayCanvas.getHeight() - newHeight) / 2;
371                        right = overlayCanvas.getWidth();
372                        bottom = top + newHeight;
373                    } else {
374                        int newWidth = ((overlayCanvas.getHeight() * overlayBitmap.getWidth())
375                                            / overlayBitmap.getHeight());
376                        left = (overlayCanvas.getWidth() - newWidth) / 2;
377                        top  = 0;
378                        right = left + newWidth;
379                        bottom = overlayCanvas.getHeight();
380                    }
381
382                    destRect = new Rect(left, top, right, bottom);
383                    srcRect = new Rect(0, 0, overlayBitmap.getWidth(), overlayBitmap.getHeight());
384                    break;
385                }
386
387                case MediaItem.RENDERING_MODE_CROPPING: {
388                    // Calculate the source rect
389                    int left, right, top, bottom;
390                    float aROverlayImage, aRCanvas;
391                    aROverlayImage = (float)(overlayBitmap.getWidth()) /
392                                     (float)(overlayBitmap.getHeight());
393                    aRCanvas = (float)(overlayCanvas.getWidth()) /
394                                    (float)(overlayCanvas.getHeight());
395                    if (aROverlayImage < aRCanvas) {
396                        int newHeight = ((overlayBitmap.getWidth() * overlayCanvas.getHeight())
397                                   / overlayCanvas.getWidth());
398
399                        left = 0;
400                        top  = (overlayBitmap.getHeight() - newHeight) / 2;
401                        right = overlayBitmap.getWidth();
402                        bottom = top + newHeight;
403                    } else {
404                        int newWidth = ((overlayBitmap.getHeight() * overlayCanvas.getWidth())
405                                    / overlayCanvas.getHeight());
406                        left = (overlayBitmap.getWidth() - newWidth) / 2;
407                        top  = 0;
408                        right = left + newWidth;
409                        bottom = overlayBitmap.getHeight();
410                    }
411
412                    srcRect = new Rect(left, top, right, bottom);
413                    destRect = new Rect(0, 0, overlayCanvas.getWidth(), overlayCanvas.getHeight());
414                    break;
415                }
416
417                default: {
418                    throw new IllegalStateException("Rendering mode: " + renderMode);
419                }
420            }
421
422            overlayCanvas.drawBitmap(overlayBitmap, srcRect, destRect, sResizePaint);
423            overlayCanvas.setBitmap(null);
424
425            /*
426             * Write to the dest file
427             */
428            String outFileName = ((OverlayFrame)overlay).getFilename();
429
430            /*
431             * Save the image to same rgb file
432             */
433            if (outFileName != null) {
434                new File(outFileName).delete();
435            }
436
437            final FileOutputStream fl = new FileOutputStream(outFileName);
438            final DataOutputStream dos = new DataOutputStream(fl);
439
440            /*
441             * Populate the rgb file with bitmap data
442             */
443            final int [] framingBuffer = new int[width];
444            ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4);
445            IntBuffer intBuffer;
446
447            byte[] array = byteBuffer.array();
448            int tmp = 0;
449            while(tmp < height) {
450                destBitmap.getPixels(framingBuffer,0,width,0,tmp,width,1);
451                intBuffer = byteBuffer.asIntBuffer();
452                intBuffer.put(framingBuffer,0,width);
453                dos.write(array);
454                tmp += 1;
455            }
456            fl.flush();
457            fl.close();
458
459            /*
460             * Set the resized RGB width and height
461             */
462            ((OverlayFrame)overlay).setResizedRGBSize(width, height);
463        }
464    }
465}
466