MediaImageItem.java revision 3f48207ea4592ec0897f2043b2042a7114f8487d
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
17
18package android.media.videoeditor;
19
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import java.util.ArrayList;
26import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
27import android.media.videoeditor.MediaArtistNativeHelper.EditSettings;
28import android.media.videoeditor.MediaArtistNativeHelper.FileType;
29import android.media.videoeditor.MediaArtistNativeHelper.Properties;
30import android.util.Log;
31import android.util.Pair;
32
33import java.io.DataOutputStream;
34import java.io.File;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.nio.ByteBuffer;
38import java.nio.IntBuffer;
39import java.lang.Math;
40import java.util.List;
41
42/**
43 * This class represents an image item on the storyboard. Note that images are
44 * scaled down to the maximum supported resolution by preserving the native
45 * aspect ratio. To learn the scaled image dimensions use
46 * {@link #getScaledWidth()} and {@link #getScaledHeight()} respectively.
47 *
48 * {@hide}
49 */
50public class MediaImageItem extends MediaItem {
51    /**
52     *  Logging
53     */
54    private static final String TAG = "MediaImageItem";
55
56    /**
57     *  The resize paint
58     */
59    private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
60
61    /**
62     *  Instance variables
63     */
64    private final int mWidth;
65    private final int mHeight;
66    private final int mAspectRatio;
67    private long mDurationMs;
68    private int mScaledWidth, mScaledHeight;
69    private String mScaledFilename;
70    private final VideoEditorImpl mVideoEditor;
71    private String mDecodedFilename;
72    private int mGeneratedClipHeight;
73    private int mGeneratedClipWidth;
74    private String mFileName;
75
76    private final MediaArtistNativeHelper mMANativeHelper;
77
78    /**
79     * This class cannot be instantiated by using the default constructor
80     */
81    @SuppressWarnings("unused")
82    private MediaImageItem() throws IOException {
83        this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER);
84    }
85
86    /**
87     * Constructor
88     *
89     * @param editor The video editor reference
90     * @param mediaItemId The media item id
91     * @param filename The image file name
92     * @param durationMs The duration of the image on the storyboard
93     * @param renderingMode The rendering mode
94     *
95     * @throws IOException
96     */
97    public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs,
98        int renderingMode) throws IOException {
99
100        super(editor, mediaItemId, filename, renderingMode);
101
102        mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext();
103        mVideoEditor = ((VideoEditorImpl)editor);
104        try {
105            final Properties properties = mMANativeHelper.getMediaProperties(filename);
106
107            switch (mMANativeHelper.getFileType(properties.fileType)) {
108                case MediaProperties.FILE_JPEG:
109                case MediaProperties.FILE_PNG: {
110                    break;
111                }
112
113                default: {
114                    throw new IllegalArgumentException("Unsupported Input File Type");
115                }
116            }
117        } catch (Exception e) {
118            throw new IllegalArgumentException("Unsupported file or file not found: " + filename);
119        }
120        mFileName = filename;
121        /**
122         *  Determine the dimensions of the image
123         */
124        final BitmapFactory.Options dbo = new BitmapFactory.Options();
125        dbo.inJustDecodeBounds = true;
126        BitmapFactory.decodeFile(filename, dbo);
127
128        mWidth = dbo.outWidth;
129        mHeight = dbo.outHeight;
130        mDurationMs = durationMs;
131        mDecodedFilename = String.format(mMANativeHelper.getProjectPath() +
132                "/" + "decoded" + getId()+ ".rgb");
133
134        try {
135            mAspectRatio = mMANativeHelper.getAspectRatio(mWidth, mHeight);
136        } catch(IllegalArgumentException e) {
137            throw new IllegalArgumentException ("Null width and height");
138        }
139
140        mGeneratedClipHeight = 0;
141        mGeneratedClipWidth = 0;
142
143        /**
144         *  Images are stored in memory scaled to the maximum resolution to
145         *  save memory.
146         */
147        final Pair<Integer, Integer>[] resolutions =
148            MediaProperties.getSupportedResolutions(mAspectRatio);
149
150        /**
151         *  Get the highest resolution
152         */
153        final Pair<Integer, Integer> maxResolution = resolutions[resolutions.length - 1];
154
155        final Bitmap imageBitmap;
156
157        if (mHeight > maxResolution.second) {
158            /**
159             *  We need to scale the image
160             */
161            imageBitmap = scaleImage(filename, maxResolution.first,
162                                                         maxResolution.second);
163            mScaledFilename = String.format(mMANativeHelper.getProjectPath() +
164                    "/" + "scaled" + getId()+ ".JPG");
165            if (!((new File(mScaledFilename)).exists())) {
166                super.mRegenerateClip = true;
167                final FileOutputStream f1 = new FileOutputStream(mScaledFilename);
168                imageBitmap.compress(Bitmap.CompressFormat.JPEG, 50,f1);
169                f1.close();
170            }
171            mScaledWidth =  (imageBitmap.getWidth() >> 1) << 1;
172            mScaledHeight = (imageBitmap.getHeight() >> 1) << 1;
173        } else {
174            mScaledFilename = filename;
175            mScaledWidth =  (mWidth >> 1) << 1;
176            mScaledHeight = (mHeight >> 1) << 1;
177            imageBitmap = BitmapFactory.decodeFile(mScaledFilename);
178        }
179        int newWidth = mScaledWidth;
180        int newHeight = mScaledHeight;
181        if (!((new File(mDecodedFilename)).exists())) {
182            final FileOutputStream fl = new FileOutputStream(mDecodedFilename);
183            final DataOutputStream dos = new DataOutputStream(fl);
184            final int [] framingBuffer = new int[newWidth];
185            final ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4);
186            IntBuffer intBuffer;
187            final byte[] array = byteBuffer.array();
188            int tmp = 0;
189            while (tmp < newHeight) {
190                imageBitmap.getPixels(framingBuffer, 0, mScaledWidth, 0,
191                                                        tmp, newWidth, 1);
192                intBuffer = byteBuffer.asIntBuffer();
193                intBuffer.put(framingBuffer, 0, newWidth);
194                dos.write(array);
195                tmp += 1;
196            }
197            fl.close();
198        }
199        imageBitmap.recycle();
200    }
201
202    /*
203     * {@inheritDoc}
204     */
205    @Override
206    public int getFileType() {
207        if (mFilename.endsWith(".jpg") || mFilename.endsWith(".jpeg")
208                || mFilename.endsWith(".JPG") || mFilename.endsWith(".JPEG")) {
209            return MediaProperties.FILE_JPEG;
210        } else if (mFilename.endsWith(".png") || mFilename.endsWith(".PNG")) {
211            return MediaProperties.FILE_PNG;
212        } else {
213            return MediaProperties.FILE_UNSUPPORTED;
214        }
215    }
216
217    /**
218     * @return The scaled image file name
219     */
220    String getScaledImageFileName() {
221        return mScaledFilename;
222    }
223
224    /**
225     * @return The generated Kenburns clip height.
226     */
227    int getGeneratedClipHeight() {
228        return mGeneratedClipHeight;
229    }
230
231    /**
232     * @return The generated Kenburns clip width.
233     */
234    int getGeneratedClipWidth() {
235        return mGeneratedClipWidth;
236    }
237
238    /**
239     * @return The file name of image which is decoded and stored
240     * in RGB format
241     */
242    String getDecodedImageFileName() {
243        return mDecodedFilename;
244    }
245
246    /*
247     * {@inheritDoc}
248     */
249    @Override
250    public int getWidth() {
251        return mWidth;
252    }
253
254    /*
255     * {@inheritDoc}
256     */
257    @Override
258    public int getHeight() {
259        return mHeight;
260    }
261
262    /**
263     * @return The scaled width of the image.
264     */
265    public int getScaledWidth() {
266        return mScaledWidth;
267    }
268
269    /**
270     * @return The scaled height of the image.
271     */
272    public int getScaledHeight() {
273        return mScaledHeight;
274    }
275
276    /*
277     * {@inheritDoc}
278     */
279    @Override
280    public int getAspectRatio() {
281        return mAspectRatio;
282    }
283
284    /**
285     * This method will adjust the duration of bounding transitions, effects
286     * and overlays if the current duration of the transactions become greater
287     * than the maximum allowable duration.
288     *
289     * @param durationMs The duration of the image in the storyboard timeline
290     */
291    public void setDuration(long durationMs) {
292        if (durationMs == mDurationMs) {
293            return;
294        }
295
296        mMANativeHelper.setGeneratePreview(true);
297
298        /**
299         * Invalidate the end transitions if necessary.
300         * This invalidation is necessary for the case in which an effect or
301         * an overlay is overlapping with the end transition
302         * (before the duration is changed) and it no longer overlaps with the
303         * transition after the duration is increased.
304         *
305         * The beginning transition does not need to be invalidated at this time
306         * because an effect or an overlay overlaps with the beginning
307         * transition, the begin transition is unaffected by a media item
308         * duration change.
309         */
310        invalidateEndTransition();
311
312        mDurationMs = durationMs;
313
314        adjustTransitions();
315        final List<Overlay> adjustedOverlays = adjustOverlays();
316        final List<Effect> adjustedEffects = adjustEffects();
317
318        /**
319         * Invalidate the beginning and end transitions after adjustments.
320         * This invalidation is necessary for the case in which an effect or
321         * an overlay was not overlapping with the beginning or end transitions
322         * before the setDuration reduces the duration of the media item and
323         * causes an overlap of the beginning and/or end transition with the
324         * effect.
325         */
326        invalidateBeginTransition(adjustedEffects, adjustedOverlays);
327        invalidateEndTransition();
328        if (getGeneratedImageClip() != null) {
329            /*
330             *  Delete the file
331             */
332            new File(getGeneratedImageClip()).delete();
333            /*
334             *  Invalidate the filename
335             */
336            setGeneratedImageClip(null);
337            super.setRegenerateClip(true);
338        }
339        mVideoEditor.updateTimelineDuration();
340    }
341
342    /**
343     * Invalidate the begin transition if any effects and overlays overlap
344     * with the begin transition.
345     *
346     * @param effects List of effects to check for transition overlap
347     * @param overlays List of overlays to check for transition overlap
348     */
349    private void invalidateBeginTransition(List<Effect> effects, List<Overlay> overlays) {
350        if (mBeginTransition != null && mBeginTransition.isGenerated()) {
351            final long transitionDurationMs = mBeginTransition.getDuration();
352
353            /**
354             *  The begin transition must be invalidated if it overlaps with
355             *  an effect.
356             */
357            for (Effect effect : effects) {
358                /**
359                 *  Check if the effect overlaps with the begin transition
360                 */
361                if (effect.getStartTime() < transitionDurationMs) {
362                    mBeginTransition.invalidate();
363                    break;
364                }
365            }
366
367            if (mBeginTransition.isGenerated()) {
368                /**
369                 *  The end transition must be invalidated if it overlaps with
370                 *  an overlay.
371                 */
372                for (Overlay overlay : overlays) {
373                    /**
374                     *  Check if the overlay overlaps with the end transition
375                     */
376                    if (overlay.getStartTime() < transitionDurationMs) {
377                        mBeginTransition.invalidate();
378                        break;
379                    }
380                }
381            }
382        }
383    }
384
385    /**
386     * Invalidate the end transition if any effects and overlays overlap
387     * with the end transition.
388     */
389    private void invalidateEndTransition() {
390        if (mEndTransition != null && mEndTransition.isGenerated()) {
391            final long transitionDurationMs = mEndTransition.getDuration();
392
393            /**
394             *  The end transition must be invalidated if it overlaps with
395             *  an effect.
396             */
397            final List<Effect> effects = getAllEffects();
398            for (Effect effect : effects) {
399                /**
400                 *  Check if the effect overlaps with the end transition
401                 */
402                if (effect.getStartTime() + effect.getDuration() >
403                    mDurationMs - transitionDurationMs) {
404                    mEndTransition.invalidate();
405                    break;
406                }
407            }
408
409            if (mEndTransition.isGenerated()) {
410                /**
411                 *  The end transition must be invalidated if it overlaps with
412                 *  an overlay.
413                 */
414                final List<Overlay> overlays = getAllOverlays();
415                for (Overlay overlay : overlays) {
416                    /**
417                     *  Check if the overlay overlaps with the end transition
418                     */
419                    if (overlay.getStartTime() + overlay.getDuration() >
420                        mDurationMs - transitionDurationMs) {
421                        mEndTransition.invalidate();
422                        break;
423                    }
424                }
425            }
426        }
427    }
428
429    /**
430     * Adjust the start time and/or duration of effects.
431     *
432     * @return The list of effects which were adjusted
433     */
434    private List<Effect> adjustEffects() {
435        final List<Effect> adjustedEffects = new ArrayList<Effect>();
436        final List<Effect> effects = getAllEffects();
437        for (Effect effect : effects) {
438            /**
439             *  Adjust the start time if necessary
440             */
441            final long effectStartTimeMs;
442            if (effect.getStartTime() > getDuration()) {
443                effectStartTimeMs = 0;
444            } else {
445                effectStartTimeMs = effect.getStartTime();
446            }
447
448            /**
449             *  Adjust the duration if necessary
450             */
451            final long effectDurationMs;
452            if (effectStartTimeMs + effect.getDuration() > getDuration()) {
453                effectDurationMs = getDuration() - effectStartTimeMs;
454            } else {
455                effectDurationMs = effect.getDuration();
456            }
457
458            if (effectStartTimeMs != effect.getStartTime() ||
459                    effectDurationMs != effect.getDuration()) {
460                effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
461                adjustedEffects.add(effect);
462            }
463        }
464
465        return adjustedEffects;
466    }
467
468    /**
469     * Adjust the start time and/or duration of overlays.
470     *
471     * @return The list of overlays which were adjusted
472     */
473    private List<Overlay> adjustOverlays() {
474        final List<Overlay> adjustedOverlays = new ArrayList<Overlay>();
475        final List<Overlay> overlays = getAllOverlays();
476        for (Overlay overlay : overlays) {
477            /**
478             *  Adjust the start time if necessary
479             */
480            final long overlayStartTimeMs;
481            if (overlay.getStartTime() > getDuration()) {
482                overlayStartTimeMs = 0;
483            } else {
484                overlayStartTimeMs = overlay.getStartTime();
485            }
486
487            /**
488             *  Adjust the duration if necessary
489             */
490            final long overlayDurationMs;
491            if (overlayStartTimeMs + overlay.getDuration() > getDuration()) {
492                overlayDurationMs = getDuration() - overlayStartTimeMs;
493            } else {
494                overlayDurationMs = overlay.getDuration();
495            }
496
497            if (overlayStartTimeMs != overlay.getStartTime() ||
498                    overlayDurationMs != overlay.getDuration()) {
499                overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
500                adjustedOverlays.add(overlay);
501            }
502        }
503
504        return adjustedOverlays;
505    }
506
507
508    /**
509     * This function sets the Ken Burn effect generated clip
510     * name.
511     *
512     * @param generatedFilePath The name of the generated clip
513     */
514    @Override
515    void setGeneratedImageClip(String generatedFilePath) {
516        super.setGeneratedImageClip(generatedFilePath);
517
518
519        // set the Kenburns clip width and height
520        mGeneratedClipHeight = getScaledHeight();
521        switch (mVideoEditor.getAspectRatio()) {
522            case MediaProperties.ASPECT_RATIO_3_2:
523                if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
524                    mGeneratedClipWidth = 720;
525                else if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
526                    mGeneratedClipWidth = 1080;
527                break;
528            case MediaProperties.ASPECT_RATIO_16_9:
529                if (mGeneratedClipHeight == MediaProperties.HEIGHT_360)
530                    mGeneratedClipWidth = 640;
531                else if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
532                    mGeneratedClipWidth = 854;
533                else if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
534                    mGeneratedClipWidth = 1280;
535                break;
536            case MediaProperties.ASPECT_RATIO_4_3:
537                if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
538                    mGeneratedClipWidth = 640;
539                if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
540                    mGeneratedClipWidth = 960;
541                break;
542            case MediaProperties.ASPECT_RATIO_5_3:
543                if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
544                    mGeneratedClipWidth = 800;
545                break;
546            case MediaProperties.ASPECT_RATIO_11_9:
547                if (mGeneratedClipHeight == MediaProperties.HEIGHT_144)
548                    mGeneratedClipWidth = 176;
549                break;
550        }
551    }
552
553    /**
554     * @return The name of the image clip
555     * generated with ken burns effect.
556     */
557    @Override
558    String getGeneratedImageClip() {
559        return super.getGeneratedImageClip();
560    }
561
562    /*
563     * {@inheritDoc}
564     */
565    @Override
566    public long getDuration() {
567        return mDurationMs;
568    }
569
570    /*
571     * {@inheritDoc}
572     */
573    @Override
574    public long getTimelineDuration() {
575        return mDurationMs;
576    }
577
578    /*
579     * {@inheritDoc}
580     */
581    @Override
582    public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {
583        if (getGeneratedImageClip() != null) {
584            return mMANativeHelper.getPixels(getGeneratedImageClip(),
585                width, height,timeMs);
586        } else {
587            return scaleImage(mFilename, width, height);
588        }
589    }
590
591    /*
592     * {@inheritDoc}
593     */
594    @Override
595    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
596        int thumbnailCount) throws IOException {
597        //KenBurns was not applied on this.
598        if (getGeneratedImageClip() == null) {
599            final Bitmap thumbnail = scaleImage(mFilename, width, height);
600            final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount];
601            for (int i = 0; i < thumbnailCount; i++) {
602                thumbnailArray[i] = thumbnail;
603            }
604
605            return thumbnailArray;
606        } else {
607            if (startMs > endMs) {
608                throw new IllegalArgumentException("Start time is greater than end time");
609            }
610
611            if (endMs > mDurationMs) {
612                throw new IllegalArgumentException("End time is greater than file duration");
613            }
614
615            if (startMs == endMs) {
616                Bitmap[] bitmap = new Bitmap[1];
617                bitmap[0] = mMANativeHelper.getPixels(getGeneratedImageClip(),
618                    width, height,startMs);
619                return bitmap;
620            }
621
622            return mMANativeHelper.getPixelsList(getGeneratedImageClip(), width,
623                height,startMs,endMs,thumbnailCount);
624        }
625    }
626
627    /*
628     * {@inheritDoc}
629     */
630    @Override
631    void invalidateTransitions(long startTimeMs, long durationMs) {
632        /**
633         *  Check if the item overlaps with the beginning and end transitions
634         */
635        if (mBeginTransition != null) {
636            if (isOverlapping(startTimeMs, durationMs, 0, mBeginTransition.getDuration())) {
637                mBeginTransition.invalidate();
638            }
639        }
640
641        if (mEndTransition != null) {
642            final long transitionDurationMs = mEndTransition.getDuration();
643            if (isOverlapping(startTimeMs, durationMs,
644                    getDuration() - transitionDurationMs, transitionDurationMs)) {
645                mEndTransition.invalidate();
646            }
647        }
648    }
649
650    /*
651     * {@inheritDoc}
652     */
653    @Override
654    void invalidateTransitions(long oldStartTimeMs, long oldDurationMs, long newStartTimeMs,
655            long newDurationMs) {
656        /**
657         *  Check if the item overlaps with the beginning and end transitions
658         */
659        if (mBeginTransition != null) {
660            final long transitionDurationMs = mBeginTransition.getDuration();
661            final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 0,
662                    transitionDurationMs);
663            final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 0,
664                    transitionDurationMs);
665            /**
666             * Invalidate transition if:
667             *
668             * 1. New item overlaps the transition, the old one did not
669             * 2. New item does not overlap the transition, the old one did
670             * 3. New and old item overlap the transition if begin or end
671             * time changed
672             */
673            if (newOverlap != oldOverlap) { // Overlap has changed
674                mBeginTransition.invalidate();
675            } else if (newOverlap) { // Both old and new overlap
676                if ((oldStartTimeMs != newStartTimeMs) ||
677                        !(oldStartTimeMs + oldDurationMs > transitionDurationMs &&
678                        newStartTimeMs + newDurationMs > transitionDurationMs)) {
679                    mBeginTransition.invalidate();
680                }
681            }
682        }
683
684        if (mEndTransition != null) {
685            final long transitionDurationMs = mEndTransition.getDuration();
686            final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs,
687                    mDurationMs - transitionDurationMs, transitionDurationMs);
688            final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs,
689                    mDurationMs - transitionDurationMs, transitionDurationMs);
690            /**
691             * Invalidate transition if:
692             *
693             * 1. New item overlaps the transition, the old one did not
694             * 2. New item does not overlap the transition, the old one did
695             * 3. New and old item overlap the transition if begin or end
696             * time changed
697             */
698            if (newOverlap != oldOverlap) { // Overlap has changed
699                mEndTransition.invalidate();
700            } else if (newOverlap) { // Both old and new overlap
701                if ((oldStartTimeMs + oldDurationMs != newStartTimeMs + newDurationMs) ||
702                        ((oldStartTimeMs > mDurationMs - transitionDurationMs) ||
703                        newStartTimeMs > mDurationMs - transitionDurationMs)) {
704                    mEndTransition.invalidate();
705                }
706            }
707        }
708    }
709
710    /**
711     * This function invalidates the rgb image clip,ken burns effect clip,
712     * and scaled image clip
713     */
714    void invalidate() {
715        if (getGeneratedImageClip() != null) {
716            new File(getGeneratedImageClip()).delete();
717            setGeneratedImageClip(null);
718            setRegenerateClip(true);
719        }
720
721        if (mScaledFilename != null) {
722            if(mFileName != mScaledFilename) {
723                new File(mScaledFilename).delete();
724            }
725            mScaledFilename = null;
726        }
727
728        if (mDecodedFilename != null) {
729            new File(mDecodedFilename).delete();
730            mDecodedFilename = null;
731        }
732    }
733
734    /**
735     * @param KenBurnEffect object.
736     * @return an Object of {@link ClipSettings} with Ken Burn settings
737     * needed to generate the clip
738     */
739    private ClipSettings getKenBurns(EffectKenBurns effectKB) {
740        int PanZoomXa;
741        int PanZoomXb;
742        int width = 0, height = 0;
743        Rect start = new Rect();
744        Rect end = new Rect();
745        ClipSettings clipSettings = null;
746        clipSettings = new ClipSettings();
747        /**
748         *  image:
749        ---------------------------------------
750       |    Xa                                  |
751       | Ya ---------------                     |
752       |    |                |                  |
753       |    |                |                  |
754       |     ---------------    Xb       ratioB |
755       |        ratioA           -------        |
756       |                  Yb    |        |      |
757       |                        |        |      |
758       |                         -------        |
759        ---------------------------------------
760         */
761
762        effectKB.getKenBurnsSettings(start, end);
763        width = getWidth();
764        height = getHeight();
765        if ((start.left < 0) || (start.left > width) || (start.right < 0) || (start.right > width)
766                || (start.top < 0) || (start.top > height) || (start.bottom < 0)
767                || (start.bottom > height) || (end.left < 0) || (end.left > width)
768                || (end.right < 0) || (end.right > width) || (end.top < 0) || (end.top > height)
769                || (end.bottom < 0) || (end.bottom > height)) {
770            throw new IllegalArgumentException("Illegal arguments for KebBurns");
771        }
772
773        if (((width - (start.right - start.left) == 0) || (height - (start.bottom - start.top) == 0))
774                && ((width - (end.right - end.left) == 0) || (height - (end.bottom - end.top) == 0))) {
775            setRegenerateClip(false);
776            clipSettings.clipPath = getDecodedImageFileName();
777            clipSettings.fileType = FileType.JPG;
778            clipSettings.beginCutTime = 0;
779            clipSettings.endCutTime = (int)getTimelineDuration();
780            clipSettings.beginCutPercent = 0;
781            clipSettings.endCutPercent = 0;
782            clipSettings.panZoomEnabled = false;
783            clipSettings.panZoomPercentStart = 0;
784            clipSettings.panZoomTopLeftXStart = 0;
785            clipSettings.panZoomTopLeftYStart = 0;
786            clipSettings.panZoomPercentEnd = 0;
787            clipSettings.panZoomTopLeftXEnd = 0;
788            clipSettings.panZoomTopLeftYEnd = 0;
789            clipSettings.mediaRendering = mMANativeHelper
790            .getMediaItemRenderingMode(getRenderingMode());
791
792            clipSettings.rgbWidth = getScaledWidth();
793            clipSettings.rgbHeight = getScaledHeight();
794
795            return clipSettings;
796        }
797
798        PanZoomXa = (1000 * start.width()) / width;
799        PanZoomXb = (1000 * end.width()) / width;
800
801        clipSettings.clipPath = getDecodedImageFileName();
802        clipSettings.fileType = mMANativeHelper.getMediaItemFileType(getFileType());
803        clipSettings.beginCutTime = 0;
804        clipSettings.endCutTime = (int)getTimelineDuration();
805        clipSettings.beginCutPercent = 0;
806        clipSettings.endCutPercent = 0;
807        clipSettings.panZoomEnabled = true;
808        clipSettings.panZoomPercentStart = PanZoomXa;
809        clipSettings.panZoomTopLeftXStart = (start.left * 1000) / width;
810        clipSettings.panZoomTopLeftYStart = (start.top * 1000) / height;
811        clipSettings.panZoomPercentEnd = PanZoomXb;
812        clipSettings.panZoomTopLeftXEnd = (end.left * 1000) / width;
813        clipSettings.panZoomTopLeftYEnd = (end.top * 1000) / height;
814        clipSettings.mediaRendering
815            = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode());
816
817        clipSettings.rgbWidth = getScaledWidth();
818        clipSettings.rgbHeight = getScaledHeight();
819
820        return clipSettings;
821    }
822
823
824    /**
825     * @param KenBurnEffect object.
826     * @return an Object of {@link ClipSettings} with Ken Burns
827     * generated clip name
828     */
829    ClipSettings generateKenburnsClip(EffectKenBurns effectKB) {
830        EditSettings editSettings = new EditSettings();
831        editSettings.clipSettingsArray = new ClipSettings[1];
832        String output = null;
833        ClipSettings clipSettings = new ClipSettings();
834        initClipSettings(clipSettings);
835        editSettings.clipSettingsArray[0] = getKenBurns(effectKB);
836        if ((getGeneratedImageClip() == null) && (getRegenerateClip())) {
837            output = mMANativeHelper.generateKenBurnsClip(editSettings, this);
838            setGeneratedImageClip(output);
839            setRegenerateClip(false);
840            clipSettings.clipPath = output;
841            clipSettings.fileType = FileType.THREE_GPP;
842
843            mGeneratedClipHeight = getScaledHeight();
844            switch (mVideoEditor.getAspectRatio()) {
845                case MediaProperties.ASPECT_RATIO_3_2:
846                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
847                        mGeneratedClipWidth = 720;
848                    else if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
849                        mGeneratedClipWidth = 1080;
850                    break;
851                case MediaProperties.ASPECT_RATIO_16_9:
852                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_360)
853                        mGeneratedClipWidth = 640;
854                    else if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
855                        mGeneratedClipWidth = 854;
856                    else if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
857                        mGeneratedClipWidth = 1280;
858                    break;
859                case MediaProperties.ASPECT_RATIO_4_3:
860                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
861                        mGeneratedClipWidth = 640;
862                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_720)
863                        mGeneratedClipWidth = 960;
864                    break;
865                case MediaProperties.ASPECT_RATIO_5_3:
866                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_480)
867                        mGeneratedClipWidth = 800;
868                    break;
869                case MediaProperties.ASPECT_RATIO_11_9:
870                    if (mGeneratedClipHeight == MediaProperties.HEIGHT_144)
871                        mGeneratedClipWidth = 176;
872                    break;
873            }
874
875        } else {
876            if (getGeneratedImageClip() == null) {
877                clipSettings.clipPath = getDecodedImageFileName();
878                clipSettings.fileType = FileType.JPG;
879
880                clipSettings.rgbWidth = getScaledWidth();
881                clipSettings.rgbHeight = getScaledHeight();
882
883            } else {
884                clipSettings.clipPath = getGeneratedImageClip();
885                clipSettings.fileType = FileType.THREE_GPP;
886            }
887        }
888        clipSettings.mediaRendering = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode());
889        clipSettings.beginCutTime = 0;
890        clipSettings.endCutTime = (int)getTimelineDuration();
891
892        return clipSettings;
893    }
894
895    /**
896     * @return an Object of {@link ClipSettings} with Image Clip
897     * properties data populated.If the image has Ken Burns effect applied,
898     * then file path contains generated image clip name with Ken Burns effect
899     */
900    ClipSettings getImageClipProperties() {
901        ClipSettings clipSettings = new ClipSettings();
902        List<Effect> effects = null;
903        EffectKenBurns effectKB = null;
904        boolean effectKBPresent = false;
905
906        effects = getAllEffects();
907        for (Effect effect : effects) {
908            if (effect instanceof EffectKenBurns) {
909                effectKB = (EffectKenBurns)effect;
910                effectKBPresent = true;
911                break;
912            }
913        }
914
915        if (effectKBPresent) {
916            clipSettings = generateKenburnsClip(effectKB);
917        } else {
918            /**
919             * Init the clip settings object
920             */
921            initClipSettings(clipSettings);
922            clipSettings.clipPath = getDecodedImageFileName();
923            clipSettings.fileType = FileType.JPG;
924            clipSettings.beginCutTime = 0;
925            clipSettings.endCutTime = (int)getTimelineDuration();
926            clipSettings.mediaRendering = mMANativeHelper
927                .getMediaItemRenderingMode(getRenderingMode());
928            clipSettings.rgbWidth = getScaledWidth();
929            clipSettings.rgbHeight = getScaledHeight();
930
931        }
932        return clipSettings;
933    }
934
935    /**
936     * Resize a bitmap to the specified width and height
937     *
938     * @param filename The filename
939     * @param width The thumbnail width
940     * @param height The thumbnail height
941     *
942     * @return The resized bitmap
943     */
944    private Bitmap scaleImage(String filename, int width, int height)
945    throws IOException {
946        final BitmapFactory.Options dbo = new BitmapFactory.Options();
947        dbo.inJustDecodeBounds = true;
948        BitmapFactory.decodeFile(filename, dbo);
949
950        final int nativeWidth = dbo.outWidth;
951        final int nativeHeight = dbo.outHeight;
952        if (Log.isLoggable(TAG, Log.DEBUG)) {
953            Log.d(TAG, "generateThumbnail: Input: " + nativeWidth + "x" + nativeHeight
954                    + ", resize to: " + width + "x" + height);
955        }
956
957        final Bitmap srcBitmap;
958        float bitmapWidth, bitmapHeight;
959        if (nativeWidth > width || nativeHeight > height) {
960            float dx = ((float)nativeWidth) / ((float)width);
961            float dy = ((float)nativeHeight) / ((float)height);
962
963            if (dx > dy) {
964                bitmapWidth = width;
965
966                if (((float)nativeHeight / dx) < (float)height) {
967                    bitmapHeight = (float)Math.ceil(nativeHeight / dx);
968                } else { // value equals the requested height
969                    bitmapHeight = (float)Math.floor(nativeHeight / dx);
970                }
971
972            } else {
973                if (((float)nativeWidth / dy) > (float)width) {
974                    bitmapWidth = (float)Math.floor(nativeWidth / dy);
975                } else { // value equals the requested width
976                    bitmapWidth = (float)Math.ceil(nativeWidth / dy);
977                }
978
979                bitmapHeight = height;
980            }
981
982            /**
983             *  Create the bitmap from file
984             */
985            if (nativeWidth / bitmapWidth > 1) {
986
987                final BitmapFactory.Options options = new BitmapFactory.Options();
988                options.inSampleSize = nativeWidth / (int)bitmapWidth;
989                srcBitmap = BitmapFactory.decodeFile(filename, options);
990            } else {
991                srcBitmap = BitmapFactory.decodeFile(filename);
992            }
993        } else {
994            bitmapWidth = width;
995            bitmapHeight = height;
996            srcBitmap = BitmapFactory.decodeFile(filename);
997
998        }
999
1000        if (srcBitmap == null) {
1001            Log.e(TAG, "generateThumbnail: Cannot decode image bytes");
1002            throw new IOException("Cannot decode file: " + mFilename);
1003        }
1004
1005        /**
1006         *  Create the canvas bitmap
1007         */
1008        final Bitmap bitmap = Bitmap.createBitmap((int)bitmapWidth,
1009                                                  (int)bitmapHeight,
1010                                                  Bitmap.Config.ARGB_8888);
1011        final Canvas canvas = new Canvas(bitmap);
1012        canvas.drawBitmap(srcBitmap, new Rect(0, 0, srcBitmap.getWidth(),
1013                                              srcBitmap.getHeight()),
1014                                              new Rect(0, 0, (int)bitmapWidth,
1015                                              (int)bitmapHeight), sResizePaint);
1016        /**
1017         *  Release the source bitmap
1018         */
1019        srcBitmap.recycle();
1020        return bitmap;
1021    }
1022}
1023