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