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