MediaImageItem.java revision 75159247d81943ba99935fe02c38383c7c380304
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media.videoeditor; 18 19import java.io.IOException; 20import java.util.List; 21 22import android.graphics.Bitmap; 23import android.graphics.BitmapFactory; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.Rect; 27import android.util.Log; 28import android.util.Pair; 29 30/** 31 * This class represents an image item on the storyboard. Note that images are 32 * scaled down to the maximum supported resolution by preserving the native 33 * aspect ratio. To learn the scaled image dimensions use 34 * {@link #getScaledWidth()} and {@link #getScaledHeight()} respectively. 35 * 36 * {@hide} 37 */ 38public class MediaImageItem extends MediaItem { 39 // Logging 40 private static final String TAG = "MediaImageItem"; 41 42 // The resize paint 43 private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG); 44 45 // Instance variables 46 private final int mWidth; 47 private final int mHeight; 48 private final int mAspectRatio; 49 private long mDurationMs; 50 private int mScaledWidth, mScaledHeight; 51 52 /** 53 * This class cannot be instantiated by using the default constructor 54 */ 55 @SuppressWarnings("unused") 56 private MediaImageItem() throws IOException { 57 this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER); 58 } 59 60 /** 61 * Constructor 62 * 63 * @param editor The video editor reference 64 * @param mediaItemId The media item id 65 * @param filename The image file name 66 * @param durationMs The duration of the image on the storyboard 67 * @param renderingMode The rendering mode 68 * 69 * @throws IOException 70 */ 71 public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs, 72 int renderingMode) 73 throws IOException { 74 super(editor, mediaItemId, filename, renderingMode); 75 76 // Determine the dimensions of the image 77 final BitmapFactory.Options dbo = new BitmapFactory.Options(); 78 dbo.inJustDecodeBounds = true; 79 BitmapFactory.decodeFile(filename, dbo); 80 81 mWidth = dbo.outWidth; 82 mHeight = dbo.outHeight; 83 mDurationMs = durationMs; 84 85 // TODO: Determine the aspect ratio from the width and height 86 mAspectRatio = MediaProperties.ASPECT_RATIO_4_3; 87 88 // Images are stored in memory scaled to the maximum resolution to 89 // save memory. 90 final Pair<Integer, Integer>[] resolutions = 91 MediaProperties.getSupportedResolutions(mAspectRatio); 92 // Get the highest resolution 93 final Pair<Integer, Integer> maxResolution = resolutions[resolutions.length - 1]; 94 if (mHeight > maxResolution.second) { 95 // We need to scale the image 96 scaleImage(filename, maxResolution.first, maxResolution.second); 97 mScaledWidth = maxResolution.first; 98 mScaledHeight = maxResolution.second; 99 } else { 100 mScaledWidth = mWidth; 101 mScaledHeight = mHeight; 102 } 103 } 104 105 /* 106 * {@inheritDoc} 107 */ 108 @Override 109 public int getFileType() { 110 if (mFilename.endsWith(".jpg") || mFilename.endsWith(".jpeg")) { 111 return MediaProperties.FILE_JPEG; 112 } else if (mFilename.endsWith(".png")) { 113 return MediaProperties.FILE_PNG; 114 } else { 115 return MediaProperties.FILE_UNSUPPORTED; 116 } 117 } 118 119 /* 120 * {@inheritDoc} 121 */ 122 @Override 123 public int getWidth() { 124 return mWidth; 125 } 126 127 /* 128 * {@inheritDoc} 129 */ 130 @Override 131 public int getHeight() { 132 return mHeight; 133 } 134 135 /** 136 * @return The scaled width of the image. 137 */ 138 public int getScaledWidth() { 139 return mScaledWidth; 140 } 141 142 /** 143 * @return The scaled height of the image. 144 */ 145 public int getScaledHeight() { 146 return mScaledHeight; 147 } 148 149 /* 150 * {@inheritDoc} 151 */ 152 @Override 153 public int getAspectRatio() { 154 return mAspectRatio; 155 } 156 157 /** 158 * This method will adjust the duration of bounding transitions, effects 159 * and overlays if the current duration of the transactions become greater 160 * than the maximum allowable duration. 161 * 162 * @param durationMs The duration of the image in the storyboard timeline 163 */ 164 public void setDuration(long durationMs) { 165 if (durationMs == mDurationMs) { 166 return; 167 } 168 169 // Invalidate the end transitions if necessary. 170 // This invalidation is necessary for the case in which an effect or 171 // an overlay is overlapping with the end transition 172 // (before the duration is changed) and it no longer overlaps with the 173 // transition after the duration is increased. 174 175 // The beginning transition does not need to be invalidated at this time 176 // because an effect or an overlay overlaps with the beginning 177 // transition, the begin transition is unaffected by a media item 178 // duration change. 179 invalidateEndTransition(); 180 181 final long oldDurationMs = mDurationMs; 182 mDurationMs = durationMs; 183 184 adjustTransitions(); 185 adjustOverlays(); 186 adjustEffects(); 187 188 // Invalidate the beginning and end transitions after adjustments. 189 // This invalidation is necessary for the case in which an effect or 190 // an overlay was not overlapping with the beginning or end transitions 191 // before the setDuration reduces the duration of the media item and 192 // causes an overlap of the beginning and/or end transition with the 193 // effect. 194 // If the duration is growing, the begin transition does not need to 195 // be invalidated since the effects, overlays are not adjusted. 196 if (mDurationMs < oldDurationMs) { 197 invalidateBeginTransition(); 198 } 199 invalidateEndTransition(); 200 } 201 202 /* 203 * {@inheritDoc} 204 */ 205 @Override 206 public long getDuration() { 207 return mDurationMs; 208 } 209 210 /* 211 * {@inheritDoc} 212 */ 213 @Override 214 public long getTimelineDuration() { 215 return mDurationMs; 216 } 217 218 /* 219 * {@inheritDoc} 220 */ 221 @Override 222 public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException { 223 return scaleImage(mFilename, width, height); 224 } 225 226 /* 227 * {@inheritDoc} 228 */ 229 @Override 230 public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 231 int thumbnailCount) throws IOException { 232 final Bitmap thumbnail = scaleImage(mFilename, width, height); 233 final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount]; 234 for (int i = 0; i < thumbnailCount; i++) { 235 thumbnailArray[i] = thumbnail; 236 } 237 return thumbnailArray; 238 } 239 240 /* 241 * {@inheritDoc} 242 */ 243 @Override 244 void invalidateTransitions(long startTimeMs, long durationMs) { 245 // Check if the effect overlaps with the beginning and end transitions 246 if (mBeginTransition != null) { 247 if (startTimeMs < mBeginTransition.getDuration()) { 248 mBeginTransition.invalidate(); 249 } 250 } 251 252 if (mEndTransition != null) { 253 if (startTimeMs + durationMs > mDurationMs - mEndTransition.getDuration()) { 254 mEndTransition.invalidate(); 255 } 256 } 257 } 258 259 /** 260 * Invalidate the begin transition if any effects and overlays overlap 261 * with the begin transition. 262 */ 263 private void invalidateBeginTransition() { 264 if (mBeginTransition != null && mBeginTransition.isGenerated()) { 265 final long transitionDurationMs = mBeginTransition.getDuration(); 266 267 // The begin transition must be invalidated if it overlaps with 268 // an effect. 269 final List<Effect> effects = getAllEffects(); 270 for (Effect effect : effects) { 271 // Check if the effect overlaps with the begin transition 272 if (effect.getStartTime() < transitionDurationMs) { 273 mBeginTransition.invalidate(); 274 break; 275 } 276 } 277 278 if (mBeginTransition.isGenerated()) { 279 // The end transition must be invalidated if it overlaps with 280 // an overlay. 281 final List<Overlay> overlays = getAllOverlays(); 282 for (Overlay overlay : overlays) { 283 // Check if the overlay overlaps with the end transition 284 if (overlay.getStartTime() < transitionDurationMs) { 285 mBeginTransition.invalidate(); 286 break; 287 } 288 } 289 } 290 } 291 } 292 293 /** 294 * Invalidate the end transition if any effects and overlays overlap 295 * with the end transition. 296 */ 297 private void invalidateEndTransition() { 298 if (mEndTransition != null && mEndTransition.isGenerated()) { 299 final long transitionDurationMs = mEndTransition.getDuration(); 300 301 // The end transition must be invalidated if it overlaps with 302 // an effect. 303 final List<Effect> effects = getAllEffects(); 304 for (Effect effect : effects) { 305 // Check if the effect overlaps with the end transition 306 if (effect.getStartTime() + effect.getDuration() > 307 mDurationMs - transitionDurationMs) { 308 mEndTransition.invalidate(); 309 break; 310 } 311 } 312 313 if (mEndTransition.isGenerated()) { 314 // The end transition must be invalidated if it overlaps with 315 // an overlay. 316 final List<Overlay> overlays = getAllOverlays(); 317 for (Overlay overlay : overlays) { 318 // Check if the overlay overlaps with the end transition 319 if (overlay.getStartTime() + overlay.getDuration() > 320 mDurationMs - transitionDurationMs) { 321 mEndTransition.invalidate(); 322 break; 323 } 324 } 325 } 326 } 327 } 328 329 /** 330 * Adjust the start time and/or duration of effects. 331 */ 332 private void adjustEffects() { 333 final List<Effect> effects = getAllEffects(); 334 for (Effect effect : effects) { 335 // Adjust the start time if necessary 336 final long effectStartTimeMs; 337 if (effect.getStartTime() > getDuration()) { 338 effectStartTimeMs = 0; 339 } else { 340 effectStartTimeMs = effect.getStartTime(); 341 } 342 343 // Adjust the duration if necessary 344 final long effectDurationMs; 345 if (effectStartTimeMs + effect.getDuration() > getDuration()) { 346 effectDurationMs = getDuration() - effectStartTimeMs; 347 } else { 348 effectDurationMs = effect.getDuration(); 349 } 350 351 if (effectStartTimeMs != effect.getStartTime() || 352 effectDurationMs != effect.getDuration()) { 353 effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs); 354 } 355 } 356 } 357 358 /** 359 * Adjust the start time and/or duration of overlays. 360 */ 361 private void adjustOverlays() { 362 final List<Overlay> overlays = getAllOverlays(); 363 for (Overlay overlay : overlays) { 364 // Adjust the start time if necessary 365 final long overlayStartTimeMs; 366 if (overlay.getStartTime() > getDuration()) { 367 overlayStartTimeMs = 0; 368 } else { 369 overlayStartTimeMs = overlay.getStartTime(); 370 } 371 372 // Adjust the duration if necessary 373 final long overlayDurationMs; 374 if (overlayStartTimeMs + overlay.getDuration() > getDuration()) { 375 overlayDurationMs = getDuration() - overlayStartTimeMs; 376 } else { 377 overlayDurationMs = overlay.getDuration(); 378 } 379 380 if (overlayStartTimeMs != overlay.getStartTime() || 381 overlayDurationMs != overlay.getDuration()) { 382 overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs); 383 } 384 } 385 } 386 387 /** 388 * Resize a bitmap to the specified width and height 389 * 390 * @param filename The filename 391 * @param width The thumbnail width 392 * @param height The thumbnail height 393 * 394 * @return The resized bitmap 395 */ 396 private Bitmap scaleImage(String filename, int width, int height) throws IOException { 397 final BitmapFactory.Options dbo = new BitmapFactory.Options(); 398 dbo.inJustDecodeBounds = true; 399 BitmapFactory.decodeFile(filename, dbo); 400 401 final int nativeWidth = dbo.outWidth; 402 final int nativeHeight = dbo.outHeight; 403 if (Log.isLoggable(TAG, Log.DEBUG)) { 404 Log.d(TAG, "generateThumbnail: Input: " + nativeWidth + "x" + nativeHeight 405 + ", resize to: " + width + "x" + height); 406 } 407 408 final Bitmap srcBitmap; 409 float bitmapWidth, bitmapHeight; 410 if (nativeWidth > width || nativeHeight > height) { 411 float dx = ((float)nativeWidth) / ((float)width); 412 float dy = ((float)nativeHeight) / ((float)height); 413 if (dx > dy) { 414 bitmapWidth = width; 415 bitmapHeight = nativeHeight / dx; 416 } else { 417 bitmapWidth = nativeWidth / dy; 418 bitmapHeight = height; 419 } 420 // Create the bitmap from file 421 if (nativeWidth / bitmapWidth > 1) { 422 final BitmapFactory.Options options = new BitmapFactory.Options(); 423 options.inSampleSize = nativeWidth / (int)bitmapWidth; 424 srcBitmap = BitmapFactory.decodeFile(filename, options); 425 } else { 426 srcBitmap = BitmapFactory.decodeFile(filename); 427 } 428 } else { 429 bitmapWidth = width; 430 bitmapHeight = height; 431 srcBitmap = BitmapFactory.decodeFile(filename); 432 } 433 434 if (srcBitmap == null) { 435 Log.e(TAG, "generateThumbnail: Cannot decode image bytes"); 436 throw new IOException("Cannot decode file: " + mFilename); 437 } 438 439 // Create the canvas bitmap 440 final Bitmap bitmap = Bitmap.createBitmap((int)bitmapWidth, (int)bitmapHeight, 441 Bitmap.Config.ARGB_8888); 442 final Canvas canvas = new Canvas(bitmap); 443 canvas.drawBitmap(srcBitmap, new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight()), 444 new Rect(0, 0, (int)bitmapWidth, (int)bitmapHeight), sResizePaint); 445 // Release the source bitmap 446 srcBitmap.recycle(); 447 return bitmap; 448 } 449} 450