Home | History | Annotate | Download | only in videoeditor
      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 
     18 package android.media.videoeditor;
     19 
     20 import android.graphics.Bitmap;
     21 import android.graphics.BitmapFactory;
     22 import android.graphics.Canvas;
     23 import android.graphics.Paint;
     24 import android.graphics.Rect;
     25 import java.util.ArrayList;
     26 import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
     27 import android.media.videoeditor.MediaArtistNativeHelper.EditSettings;
     28 import android.media.videoeditor.MediaArtistNativeHelper.FileType;
     29 import android.media.videoeditor.MediaArtistNativeHelper.Properties;
     30 import android.util.Log;
     31 import android.util.Pair;
     32 
     33 import java.io.DataOutputStream;
     34 import java.io.File;
     35 import java.io.FileOutputStream;
     36 import java.io.IOException;
     37 import java.nio.ByteBuffer;
     38 import java.nio.IntBuffer;
     39 import java.lang.Math;
     40 import 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  */
     50 public 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