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