MediaImageItem.java revision 6311d0a079702b29984c0d31937345be105e1a5e
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     * This function get the proper width by given aspect ratio
508     * and height.
509     *
510     * @param aspectRatio  Given aspect ratio
511     * @param height  Given height
512     */
513    private int getWidthByAspectRatioAndHeight(int aspectRatio, int height) {
514        int width = 0;
515
516        switch (aspectRatio) {
517            case MediaProperties.ASPECT_RATIO_3_2:
518                if (height == MediaProperties.HEIGHT_480)
519                    width = 720;
520                else if (height == MediaProperties.HEIGHT_720)
521                    width = 1080;
522                break;
523
524            case MediaProperties.ASPECT_RATIO_16_9:
525                if (height == MediaProperties.HEIGHT_360)
526                    width = 640;
527                else if (height == MediaProperties.HEIGHT_480)
528                    width = 854;
529                else if (height == MediaProperties.HEIGHT_720)
530                    width = 1280;
531                else if (height == MediaProperties.HEIGHT_1080)
532                    width = 1920;
533                break;
534
535            case MediaProperties.ASPECT_RATIO_4_3:
536                if (height == MediaProperties.HEIGHT_480)
537                    width = 640;
538                if (height == MediaProperties.HEIGHT_720)
539                    width = 960;
540                break;
541
542            case MediaProperties.ASPECT_RATIO_5_3:
543                if (height == MediaProperties.HEIGHT_480)
544                    width = 800;
545                break;
546
547            case MediaProperties.ASPECT_RATIO_11_9:
548                if (height == MediaProperties.HEIGHT_144)
549                    width = 176;
550                break;
551
552            default : {
553                throw new IllegalArgumentException(
554                    "Illegal arguments for aspectRatio");
555            }
556        }
557
558        return width;
559    }
560
561    /**
562     * This function sets the Ken Burn effect generated clip
563     * name.
564     *
565     * @param generatedFilePath The name of the generated clip
566     */
567    @Override
568    void setGeneratedImageClip(String generatedFilePath) {
569        super.setGeneratedImageClip(generatedFilePath);
570
571        // set the Kenburns clip width and height
572        mGeneratedClipHeight = getScaledHeight();
573        mGeneratedClipWidth = getWidthByAspectRatioAndHeight(
574                mVideoEditor.getAspectRatio(), mGeneratedClipHeight);
575    }
576
577    /**
578     * @return The name of the image clip
579     * generated with ken burns effect.
580     */
581    @Override
582    String getGeneratedImageClip() {
583        return super.getGeneratedImageClip();
584    }
585
586    /*
587     * {@inheritDoc}
588     */
589    @Override
590    public long getDuration() {
591        return mDurationMs;
592    }
593
594    /*
595     * {@inheritDoc}
596     */
597    @Override
598    public long getTimelineDuration() {
599        return mDurationMs;
600    }
601
602    /*
603     * {@inheritDoc}
604     */
605    @Override
606    public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {
607        if (getGeneratedImageClip() != null) {
608            return mMANativeHelper.getPixels(getGeneratedImageClip(),
609                width, height,timeMs);
610        } else {
611            return scaleImage(mFilename, width, height);
612        }
613    }
614
615    /*
616     * {@inheritDoc}
617     */
618    @Override
619    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
620        int thumbnailCount) throws IOException {
621        //KenBurns was not applied on this.
622        if (getGeneratedImageClip() == null) {
623            final Bitmap thumbnail = scaleImage(mFilename, width, height);
624            final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount];
625            for (int i = 0; i < thumbnailCount; i++) {
626                thumbnailArray[i] = thumbnail;
627            }
628
629            return thumbnailArray;
630        } else {
631            if (startMs > endMs) {
632                throw new IllegalArgumentException("Start time is greater than end time");
633            }
634
635            if (endMs > mDurationMs) {
636                throw new IllegalArgumentException("End time is greater than file duration");
637            }
638
639            if (startMs == endMs) {
640                Bitmap[] bitmap = new Bitmap[1];
641                bitmap[0] = mMANativeHelper.getPixels(getGeneratedImageClip(),
642                    width, height,startMs);
643                return bitmap;
644            }
645
646            return mMANativeHelper.getPixelsList(getGeneratedImageClip(), width,
647                height,startMs,endMs,thumbnailCount);
648        }
649    }
650
651    /*
652     * {@inheritDoc}
653     */
654    @Override
655    void invalidateTransitions(long startTimeMs, long durationMs) {
656        /**
657         *  Check if the item overlaps with the beginning and end transitions
658         */
659        if (mBeginTransition != null) {
660            if (isOverlapping(startTimeMs, durationMs, 0, mBeginTransition.getDuration())) {
661                mBeginTransition.invalidate();
662            }
663        }
664
665        if (mEndTransition != null) {
666            final long transitionDurationMs = mEndTransition.getDuration();
667            if (isOverlapping(startTimeMs, durationMs,
668                    getDuration() - transitionDurationMs, transitionDurationMs)) {
669                mEndTransition.invalidate();
670            }
671        }
672    }
673
674    /*
675     * {@inheritDoc}
676     */
677    @Override
678    void invalidateTransitions(long oldStartTimeMs, long oldDurationMs, long newStartTimeMs,
679            long newDurationMs) {
680        /**
681         *  Check if the item overlaps with the beginning and end transitions
682         */
683        if (mBeginTransition != null) {
684            final long transitionDurationMs = mBeginTransition.getDuration();
685            final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 0,
686                    transitionDurationMs);
687            final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 0,
688                    transitionDurationMs);
689            /**
690             * Invalidate transition if:
691             *
692             * 1. New item overlaps the transition, the old one did not
693             * 2. New item does not overlap the transition, the old one did
694             * 3. New and old item overlap the transition if begin or end
695             * time changed
696             */
697            if (newOverlap != oldOverlap) { // Overlap has changed
698                mBeginTransition.invalidate();
699            } else if (newOverlap) { // Both old and new overlap
700                if ((oldStartTimeMs != newStartTimeMs) ||
701                        !(oldStartTimeMs + oldDurationMs > transitionDurationMs &&
702                        newStartTimeMs + newDurationMs > transitionDurationMs)) {
703                    mBeginTransition.invalidate();
704                }
705            }
706        }
707
708        if (mEndTransition != null) {
709            final long transitionDurationMs = mEndTransition.getDuration();
710            final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs,
711                    mDurationMs - transitionDurationMs, transitionDurationMs);
712            final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs,
713                    mDurationMs - transitionDurationMs, transitionDurationMs);
714            /**
715             * Invalidate transition if:
716             *
717             * 1. New item overlaps the transition, the old one did not
718             * 2. New item does not overlap the transition, the old one did
719             * 3. New and old item overlap the transition if begin or end
720             * time changed
721             */
722            if (newOverlap != oldOverlap) { // Overlap has changed
723                mEndTransition.invalidate();
724            } else if (newOverlap) { // Both old and new overlap
725                if ((oldStartTimeMs + oldDurationMs != newStartTimeMs + newDurationMs) ||
726                        ((oldStartTimeMs > mDurationMs - transitionDurationMs) ||
727                        newStartTimeMs > mDurationMs - transitionDurationMs)) {
728                    mEndTransition.invalidate();
729                }
730            }
731        }
732    }
733
734    /**
735     * This function invalidates the rgb image clip,ken burns effect clip,
736     * and scaled image clip
737     */
738    void invalidate() {
739        if (getGeneratedImageClip() != null) {
740            new File(getGeneratedImageClip()).delete();
741            setGeneratedImageClip(null);
742            setRegenerateClip(true);
743        }
744
745        if (mScaledFilename != null) {
746            if(mFileName != mScaledFilename) {
747                new File(mScaledFilename).delete();
748            }
749            mScaledFilename = null;
750        }
751
752        if (mDecodedFilename != null) {
753            new File(mDecodedFilename).delete();
754            mDecodedFilename = null;
755        }
756    }
757
758    /**
759     * @param KenBurnEffect object.
760     * @return an Object of {@link ClipSettings} with Ken Burn settings
761     * needed to generate the clip
762     */
763    private ClipSettings getKenBurns(EffectKenBurns effectKB) {
764        int PanZoomXa;
765        int PanZoomXb;
766        int width = 0, height = 0;
767        Rect start = new Rect();
768        Rect end = new Rect();
769        ClipSettings clipSettings = null;
770        clipSettings = new ClipSettings();
771        /**
772         *  image:
773        ---------------------------------------
774       |    Xa                                  |
775       | Ya ---------------                     |
776       |    |                |                  |
777       |    |                |                  |
778       |     ---------------    Xb       ratioB |
779       |        ratioA           -------        |
780       |                  Yb    |        |      |
781       |                        |        |      |
782       |                         -------        |
783        ---------------------------------------
784         */
785
786        effectKB.getKenBurnsSettings(start, end);
787        width = getWidth();
788        height = getHeight();
789        if ((start.left < 0) || (start.left > width) || (start.right < 0) || (start.right > width)
790                || (start.top < 0) || (start.top > height) || (start.bottom < 0)
791                || (start.bottom > height) || (end.left < 0) || (end.left > width)
792                || (end.right < 0) || (end.right > width) || (end.top < 0) || (end.top > height)
793                || (end.bottom < 0) || (end.bottom > height)) {
794            throw new IllegalArgumentException("Illegal arguments for KebBurns");
795        }
796
797        if (((width - (start.right - start.left) == 0) || (height - (start.bottom - start.top) == 0))
798                && ((width - (end.right - end.left) == 0) || (height - (end.bottom - end.top) == 0))) {
799            setRegenerateClip(false);
800            clipSettings.clipPath = getDecodedImageFileName();
801            clipSettings.fileType = FileType.JPG;
802            clipSettings.beginCutTime = 0;
803            clipSettings.endCutTime = (int)getTimelineDuration();
804            clipSettings.beginCutPercent = 0;
805            clipSettings.endCutPercent = 0;
806            clipSettings.panZoomEnabled = false;
807            clipSettings.panZoomPercentStart = 0;
808            clipSettings.panZoomTopLeftXStart = 0;
809            clipSettings.panZoomTopLeftYStart = 0;
810            clipSettings.panZoomPercentEnd = 0;
811            clipSettings.panZoomTopLeftXEnd = 0;
812            clipSettings.panZoomTopLeftYEnd = 0;
813            clipSettings.mediaRendering = mMANativeHelper
814            .getMediaItemRenderingMode(getRenderingMode());
815
816            clipSettings.rgbWidth = getScaledWidth();
817            clipSettings.rgbHeight = getScaledHeight();
818
819            return clipSettings;
820        }
821
822        PanZoomXa = (1000 * start.width()) / width;
823        PanZoomXb = (1000 * end.width()) / width;
824
825        clipSettings.clipPath = getDecodedImageFileName();
826        clipSettings.fileType = mMANativeHelper.getMediaItemFileType(getFileType());
827        clipSettings.beginCutTime = 0;
828        clipSettings.endCutTime = (int)getTimelineDuration();
829        clipSettings.beginCutPercent = 0;
830        clipSettings.endCutPercent = 0;
831        clipSettings.panZoomEnabled = true;
832        clipSettings.panZoomPercentStart = PanZoomXa;
833        clipSettings.panZoomTopLeftXStart = (start.left * 1000) / width;
834        clipSettings.panZoomTopLeftYStart = (start.top * 1000) / height;
835        clipSettings.panZoomPercentEnd = PanZoomXb;
836        clipSettings.panZoomTopLeftXEnd = (end.left * 1000) / width;
837        clipSettings.panZoomTopLeftYEnd = (end.top * 1000) / height;
838        clipSettings.mediaRendering
839            = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode());
840
841        clipSettings.rgbWidth = getScaledWidth();
842        clipSettings.rgbHeight = getScaledHeight();
843
844        return clipSettings;
845    }
846
847
848    /**
849     * @param KenBurnEffect object.
850     * @return an Object of {@link ClipSettings} with Ken Burns
851     * generated clip name
852     */
853    ClipSettings generateKenburnsClip(EffectKenBurns effectKB) {
854        EditSettings editSettings = new EditSettings();
855        editSettings.clipSettingsArray = new ClipSettings[1];
856        String output = null;
857        ClipSettings clipSettings = new ClipSettings();
858        initClipSettings(clipSettings);
859        editSettings.clipSettingsArray[0] = getKenBurns(effectKB);
860        if ((getGeneratedImageClip() == null) && (getRegenerateClip())) {
861            output = mMANativeHelper.generateKenBurnsClip(editSettings, this);
862            setGeneratedImageClip(output);
863            setRegenerateClip(false);
864            clipSettings.clipPath = output;
865            clipSettings.fileType = FileType.THREE_GPP;
866
867            mGeneratedClipHeight = getScaledHeight();
868            mGeneratedClipWidth = getWidthByAspectRatioAndHeight(
869                    mVideoEditor.getAspectRatio(), mGeneratedClipHeight);
870        } else {
871            if (getGeneratedImageClip() == null) {
872                clipSettings.clipPath = getDecodedImageFileName();
873                clipSettings.fileType = FileType.JPG;
874
875                clipSettings.rgbWidth = getScaledWidth();
876                clipSettings.rgbHeight = getScaledHeight();
877
878            } else {
879                clipSettings.clipPath = getGeneratedImageClip();
880                clipSettings.fileType = FileType.THREE_GPP;
881            }
882        }
883        clipSettings.mediaRendering = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode());
884        clipSettings.beginCutTime = 0;
885        clipSettings.endCutTime = (int)getTimelineDuration();
886
887        return clipSettings;
888    }
889
890    /**
891     * @return an Object of {@link ClipSettings} with Image Clip
892     * properties data populated.If the image has Ken Burns effect applied,
893     * then file path contains generated image clip name with Ken Burns effect
894     */
895    ClipSettings getImageClipProperties() {
896        ClipSettings clipSettings = new ClipSettings();
897        List<Effect> effects = null;
898        EffectKenBurns effectKB = null;
899        boolean effectKBPresent = false;
900
901        effects = getAllEffects();
902        for (Effect effect : effects) {
903            if (effect instanceof EffectKenBurns) {
904                effectKB = (EffectKenBurns)effect;
905                effectKBPresent = true;
906                break;
907            }
908        }
909
910        if (effectKBPresent) {
911            clipSettings = generateKenburnsClip(effectKB);
912        } else {
913            /**
914             * Init the clip settings object
915             */
916            initClipSettings(clipSettings);
917            clipSettings.clipPath = getDecodedImageFileName();
918            clipSettings.fileType = FileType.JPG;
919            clipSettings.beginCutTime = 0;
920            clipSettings.endCutTime = (int)getTimelineDuration();
921            clipSettings.mediaRendering = mMANativeHelper
922                .getMediaItemRenderingMode(getRenderingMode());
923            clipSettings.rgbWidth = getScaledWidth();
924            clipSettings.rgbHeight = getScaledHeight();
925
926        }
927        return clipSettings;
928    }
929
930    /**
931     * Resize a bitmap to the specified width and height
932     *
933     * @param filename The filename
934     * @param width The thumbnail width
935     * @param height The thumbnail height
936     *
937     * @return The resized bitmap
938     */
939    private Bitmap scaleImage(String filename, int width, int height)
940    throws IOException {
941        final BitmapFactory.Options dbo = new BitmapFactory.Options();
942        dbo.inJustDecodeBounds = true;
943        BitmapFactory.decodeFile(filename, dbo);
944
945        final int nativeWidth = dbo.outWidth;
946        final int nativeHeight = dbo.outHeight;
947        if (Log.isLoggable(TAG, Log.DEBUG)) {
948            Log.d(TAG, "generateThumbnail: Input: " + nativeWidth + "x" + nativeHeight
949                    + ", resize to: " + width + "x" + height);
950        }
951
952        final Bitmap srcBitmap;
953        float bitmapWidth, bitmapHeight;
954        if (nativeWidth > width || nativeHeight > height) {
955            float dx = ((float)nativeWidth) / ((float)width);
956            float dy = ((float)nativeHeight) / ((float)height);
957
958            if (dx > dy) {
959                bitmapWidth = width;
960
961                if (((float)nativeHeight / dx) < (float)height) {
962                    bitmapHeight = (float)Math.ceil(nativeHeight / dx);
963                } else { // value equals the requested height
964                    bitmapHeight = (float)Math.floor(nativeHeight / dx);
965                }
966
967            } else {
968                if (((float)nativeWidth / dy) > (float)width) {
969                    bitmapWidth = (float)Math.floor(nativeWidth / dy);
970                } else { // value equals the requested width
971                    bitmapWidth = (float)Math.ceil(nativeWidth / dy);
972                }
973
974                bitmapHeight = height;
975            }
976
977            /**
978             *  Create the bitmap from file
979             */
980            if (nativeWidth / bitmapWidth > 1) {
981
982                final BitmapFactory.Options options = new BitmapFactory.Options();
983                options.inSampleSize = nativeWidth / (int)bitmapWidth;
984                srcBitmap = BitmapFactory.decodeFile(filename, options);
985            } else {
986                srcBitmap = BitmapFactory.decodeFile(filename);
987            }
988        } else {
989            bitmapWidth = width;
990            bitmapHeight = height;
991            srcBitmap = BitmapFactory.decodeFile(filename);
992
993        }
994
995        if (srcBitmap == null) {
996            Log.e(TAG, "generateThumbnail: Cannot decode image bytes");
997            throw new IOException("Cannot decode file: " + mFilename);
998        }
999
1000        /**
1001         *  Create the canvas bitmap
1002         */
1003        final Bitmap bitmap = Bitmap.createBitmap((int)bitmapWidth,
1004                                                  (int)bitmapHeight,
1005                                                  Bitmap.Config.ARGB_8888);
1006        final Canvas canvas = new Canvas(bitmap);
1007        canvas.drawBitmap(srcBitmap, new Rect(0, 0, srcBitmap.getWidth(),
1008                                              srcBitmap.getHeight()),
1009                                              new Rect(0, 0, (int)bitmapWidth,
1010                                              (int)bitmapHeight), sResizePaint);
1011        canvas.setBitmap(null);
1012        /**
1013         *  Release the source bitmap
1014         */
1015        srcBitmap.recycle();
1016        return bitmap;
1017    }
1018}
1019