1 /* 2 * Copyright (C) 2017 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 package android.graphics; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ContentResolver; 22 import android.content.res.AssetManager.AssetInputStream; 23 import android.content.res.Resources; 24 import android.graphics.drawable.AnimatedImageDrawable; 25 import android.graphics.drawable.Drawable; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.net.Uri; 28 import android.util.DisplayMetrics; 29 import android.util.Size; 30 import android.util.TypedValue; 31 32 import java.nio.ByteBuffer; 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.lang.ArrayIndexOutOfBoundsException; 37 import java.lang.AutoCloseable; 38 import java.lang.NullPointerException; 39 import java.lang.annotation.Retention; 40 import static java.lang.annotation.RetentionPolicy.SOURCE; 41 42 /** 43 * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. 44 */ 45 public final class ImageDecoder implements AutoCloseable { 46 47 /** 48 * Source of the encoded image data. 49 */ 50 public static abstract class Source { 51 private Source() {} 52 53 /* @hide */ 54 @Nullable 55 Resources getResources() { return null; } 56 57 /* @hide */ 58 int getDensity() { return Bitmap.DENSITY_NONE; } 59 60 /* @hide */ 61 int computeDstDensity() { 62 Resources res = getResources(); 63 if (res == null) { 64 return Bitmap.getDefaultDensity(); 65 } 66 67 return res.getDisplayMetrics().densityDpi; 68 } 69 70 /* @hide */ 71 @NonNull 72 abstract ImageDecoder createImageDecoder() throws IOException; 73 }; 74 75 private static class ByteArraySource extends Source { 76 ByteArraySource(@NonNull byte[] data, int offset, int length) { 77 mData = data; 78 mOffset = offset; 79 mLength = length; 80 }; 81 private final byte[] mData; 82 private final int mOffset; 83 private final int mLength; 84 85 @Override 86 public ImageDecoder createImageDecoder() throws IOException { 87 return new ImageDecoder(); 88 } 89 } 90 91 private static class ByteBufferSource extends Source { 92 ByteBufferSource(@NonNull ByteBuffer buffer) { 93 mBuffer = buffer; 94 } 95 private final ByteBuffer mBuffer; 96 97 @Override 98 public ImageDecoder createImageDecoder() throws IOException { 99 return new ImageDecoder(); 100 } 101 } 102 103 private static class ContentResolverSource extends Source { 104 ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) { 105 mResolver = resolver; 106 mUri = uri; 107 } 108 109 private final ContentResolver mResolver; 110 private final Uri mUri; 111 112 @Override 113 public ImageDecoder createImageDecoder() throws IOException { 114 return new ImageDecoder(); 115 } 116 } 117 118 /** 119 * For backwards compatibility, this does *not* close the InputStream. 120 */ 121 private static class InputStreamSource extends Source { 122 InputStreamSource(Resources res, InputStream is, int inputDensity) { 123 if (is == null) { 124 throw new IllegalArgumentException("The InputStream cannot be null"); 125 } 126 mResources = res; 127 mInputStream = is; 128 mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE; 129 } 130 131 final Resources mResources; 132 InputStream mInputStream; 133 final int mInputDensity; 134 135 @Override 136 public Resources getResources() { return mResources; } 137 138 @Override 139 public int getDensity() { return mInputDensity; } 140 141 @Override 142 public ImageDecoder createImageDecoder() throws IOException { 143 return new ImageDecoder(); 144 } 145 } 146 147 /** 148 * Takes ownership of the AssetInputStream. 149 * 150 * @hide 151 */ 152 public static class AssetInputStreamSource extends Source { 153 public AssetInputStreamSource(@NonNull AssetInputStream ais, 154 @NonNull Resources res, @NonNull TypedValue value) { 155 mAssetInputStream = ais; 156 mResources = res; 157 158 if (value.density == TypedValue.DENSITY_DEFAULT) { 159 mDensity = DisplayMetrics.DENSITY_DEFAULT; 160 } else if (value.density != TypedValue.DENSITY_NONE) { 161 mDensity = value.density; 162 } else { 163 mDensity = Bitmap.DENSITY_NONE; 164 } 165 } 166 167 private AssetInputStream mAssetInputStream; 168 private final Resources mResources; 169 private final int mDensity; 170 171 @Override 172 public Resources getResources() { return mResources; } 173 174 @Override 175 public int getDensity() { 176 return mDensity; 177 } 178 179 @Override 180 public ImageDecoder createImageDecoder() throws IOException { 181 return new ImageDecoder(); 182 } 183 } 184 185 private static class ResourceSource extends Source { 186 ResourceSource(@NonNull Resources res, int resId) { 187 mResources = res; 188 mResId = resId; 189 mResDensity = Bitmap.DENSITY_NONE; 190 } 191 192 final Resources mResources; 193 final int mResId; 194 int mResDensity; 195 196 @Override 197 public Resources getResources() { return mResources; } 198 199 @Override 200 public int getDensity() { return mResDensity; } 201 202 @Override 203 public ImageDecoder createImageDecoder() throws IOException { 204 return new ImageDecoder(); 205 } 206 } 207 208 private static class FileSource extends Source { 209 FileSource(@NonNull File file) { 210 mFile = file; 211 } 212 213 private final File mFile; 214 215 @Override 216 public ImageDecoder createImageDecoder() throws IOException { 217 return new ImageDecoder(); 218 } 219 } 220 221 /** 222 * Contains information about the encoded image. 223 */ 224 public static class ImageInfo { 225 private ImageDecoder mDecoder; 226 227 private ImageInfo(@NonNull ImageDecoder decoder) { 228 mDecoder = decoder; 229 } 230 231 /** 232 * Size of the image, without scaling or cropping. 233 */ 234 @NonNull 235 public Size getSize() { 236 return new Size(0, 0); 237 } 238 239 /** 240 * The mimeType of the image. 241 */ 242 @NonNull 243 public String getMimeType() { 244 return ""; 245 } 246 247 /** 248 * Whether the image is animated. 249 * 250 * <p>Calling {@link #decodeDrawable} will return an 251 * {@link AnimatedImageDrawable}.</p> 252 */ 253 public boolean isAnimated() { 254 return mDecoder.mAnimated; 255 } 256 }; 257 258 /** 259 * Thrown if the provided data is incomplete. 260 */ 261 public static class IncompleteException extends IOException {}; 262 263 /** 264 * Optional listener supplied to {@link #decodeDrawable} or 265 * {@link #decodeBitmap}. 266 */ 267 public interface OnHeaderDecodedListener { 268 /** 269 * Called when the header is decoded and the size is known. 270 * 271 * @param decoder allows changing the default settings of the decode. 272 * @param info Information about the encoded image. 273 * @param source that created the decoder. 274 */ 275 void onHeaderDecoded(@NonNull ImageDecoder decoder, 276 @NonNull ImageInfo info, @NonNull Source source); 277 278 }; 279 280 /** 281 * An Exception was thrown reading the {@link Source}. 282 */ 283 public static final int ERROR_SOURCE_EXCEPTION = 1; 284 285 /** 286 * The encoded data was incomplete. 287 */ 288 public static final int ERROR_SOURCE_INCOMPLETE = 2; 289 290 /** 291 * The encoded data contained an error. 292 */ 293 public static final int ERROR_SOURCE_ERROR = 3; 294 295 @Retention(SOURCE) 296 public @interface Error {} 297 298 /** 299 * Optional listener supplied to the ImageDecoder. 300 * 301 * Without this listener, errors will throw {@link java.io.IOException}. 302 */ 303 public interface OnPartialImageListener { 304 /** 305 * Called when there is only a partial image to display. 306 * 307 * If decoding is interrupted after having decoded a partial image, 308 * this listener lets the client know that and allows them to 309 * optionally finish the rest of the decode/creation process to create 310 * a partial {@link Drawable}/{@link Bitmap}. 311 * 312 * @param error indicating what interrupted the decode. 313 * @param source that had the error. 314 * @return True to create and return a {@link Drawable}/{@link Bitmap} 315 * with partial data. False (which is the default) to abort the 316 * decode and throw {@link java.io.IOException}. 317 */ 318 boolean onPartialImage(@Error int error, @NonNull Source source); 319 } 320 321 private boolean mAnimated; 322 private Rect mOutPaddingRect; 323 324 public ImageDecoder() { 325 mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable 326 } 327 328 /** 329 * Create a new {@link Source} from an asset. 330 * @hide 331 * 332 * @param res the {@link Resources} object containing the image data. 333 * @param resId resource ID of the image data. 334 * // FIXME: Can be an @DrawableRes? 335 * @return a new Source object, which can be passed to 336 * {@link #decodeDrawable} or {@link #decodeBitmap}. 337 */ 338 @NonNull 339 public static Source createSource(@NonNull Resources res, int resId) 340 { 341 return new ResourceSource(res, resId); 342 } 343 344 /** 345 * Create a new {@link Source} from a {@link android.net.Uri}. 346 * 347 * @param cr to retrieve from. 348 * @param uri of the image file. 349 * @return a new Source object, which can be passed to 350 * {@link #decodeDrawable} or {@link #decodeBitmap}. 351 */ 352 @NonNull 353 public static Source createSource(@NonNull ContentResolver cr, 354 @NonNull Uri uri) { 355 return new ContentResolverSource(cr, uri); 356 } 357 358 /** 359 * Create a new {@link Source} from a byte array. 360 * 361 * @param data byte array of compressed image data. 362 * @param offset offset into data for where the decoder should begin 363 * parsing. 364 * @param length number of bytes, beginning at offset, to parse. 365 * @throws NullPointerException if data is null. 366 * @throws ArrayIndexOutOfBoundsException if offset and length are 367 * not within data. 368 * @hide 369 */ 370 @NonNull 371 public static Source createSource(@NonNull byte[] data, int offset, 372 int length) throws ArrayIndexOutOfBoundsException { 373 if (offset < 0 || length < 0 || offset >= data.length || 374 offset + length > data.length) { 375 throw new ArrayIndexOutOfBoundsException( 376 "invalid offset/length!"); 377 } 378 return new ByteArraySource(data, offset, length); 379 } 380 381 /** 382 * See {@link #createSource(byte[], int, int). 383 * @hide 384 */ 385 @NonNull 386 public static Source createSource(@NonNull byte[] data) { 387 return createSource(data, 0, data.length); 388 } 389 390 /** 391 * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. 392 * 393 * <p>The returned {@link Source} effectively takes ownership of the 394 * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after 395 * this call.</p> 396 * 397 * Decoding will start from {@link java.nio.ByteBuffer#position()}. The 398 * position after decoding is undefined. 399 */ 400 @NonNull 401 public static Source createSource(@NonNull ByteBuffer buffer) { 402 return new ByteBufferSource(buffer); 403 } 404 405 /** 406 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 407 * @hide 408 */ 409 public static Source createSource(Resources res, InputStream is) { 410 return new InputStreamSource(res, is, Bitmap.getDefaultDensity()); 411 } 412 413 /** 414 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 415 * @hide 416 */ 417 public static Source createSource(Resources res, InputStream is, int density) { 418 return new InputStreamSource(res, is, density); 419 } 420 421 /** 422 * Create a new {@link Source} from a {@link java.io.File}. 423 */ 424 @NonNull 425 public static Source createSource(@NonNull File file) { 426 return new FileSource(file); 427 } 428 429 /** 430 * Return the width and height of a given sample size. 431 * 432 * <p>This takes an input that functions like 433 * {@link BitmapFactory.Options#inSampleSize}. It returns a width and 434 * height that can be acheived by sampling the encoded image. Other widths 435 * and heights may be supported, but will require an additional (internal) 436 * scaling step. Such internal scaling is *not* supported with 437 * {@link #setRequireUnpremultiplied} set to {@code true}.</p> 438 * 439 * @param sampleSize Sampling rate of the encoded image. 440 * @return {@link android.util.Size} of the width and height after 441 * sampling. 442 */ 443 @NonNull 444 public Size getSampledSize(int sampleSize) { 445 return new Size(0, 0); 446 } 447 448 // Modifiers 449 /** 450 * Resize the output to have the following size. 451 * 452 * @param width must be greater than 0. 453 * @param height must be greater than 0. 454 */ 455 public void setResize(int width, int height) { 456 } 457 458 /** 459 * Resize based on a sample size. 460 * 461 * <p>This has the same effect as passing the result of 462 * {@link #getSampledSize} to {@link #setResize(int, int)}.</p> 463 * 464 * @param sampleSize Sampling rate of the encoded image. 465 */ 466 public void setResize(int sampleSize) { 467 } 468 469 // These need to stay in sync with ImageDecoder.cpp's Allocator enum. 470 /** 471 * Use the default allocation for the pixel memory. 472 * 473 * Will typically result in a {@link Bitmap.Config#HARDWARE} 474 * allocation, but may be software for small images. In addition, this will 475 * switch to software when HARDWARE is incompatible, e.g. 476 * {@link #setMutable}, {@link #setAsAlphaMask}. 477 */ 478 public static final int ALLOCATOR_DEFAULT = 0; 479 480 /** 481 * Use a software allocation for the pixel memory. 482 * 483 * Useful for drawing to a software {@link Canvas} or for 484 * accessing the pixels on the final output. 485 */ 486 public static final int ALLOCATOR_SOFTWARE = 1; 487 488 /** 489 * Use shared memory for the pixel memory. 490 * 491 * Useful for sharing across processes. 492 */ 493 public static final int ALLOCATOR_SHARED_MEMORY = 2; 494 495 /** 496 * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. 497 * 498 * When this is combined with incompatible options, like 499 * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable} 500 * / {@link #decodeBitmap} will throw an 501 * {@link java.lang.IllegalStateException}. 502 */ 503 public static final int ALLOCATOR_HARDWARE = 3; 504 505 /** @hide **/ 506 @Retention(SOURCE) 507 public @interface Allocator {}; 508 509 /** 510 * Choose the backing for the pixel memory. 511 * 512 * This is ignored for animated drawables. 513 * 514 * @param allocator Type of allocator to use. 515 */ 516 public ImageDecoder setAllocator(@Allocator int allocator) { 517 return this; 518 } 519 520 /** 521 * Specify whether the {@link Bitmap} should have unpremultiplied pixels. 522 * 523 * By default, ImageDecoder will create a {@link Bitmap} with 524 * premultiplied pixels, which is required for drawing with the 525 * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling 526 * this method with a value of {@code true} will result in 527 * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied 528 * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with 529 * {@link #decodeDrawable}; attempting to decode an unpremultiplied 530 * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. 531 */ 532 public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) { 533 return this; 534 } 535 536 /** 537 * Modify the image after decoding and scaling. 538 * 539 * <p>This allows adding effects prior to returning a {@link Drawable} or 540 * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap}, 541 * this is the only way to process the image after decoding.</p> 542 * 543 * <p>If set on a nine-patch image, the nine-patch data is ignored.</p> 544 * 545 * <p>For an animated image, the drawing commands drawn on the 546 * {@link Canvas} will be recorded immediately and then applied to each 547 * frame.</p> 548 */ 549 public ImageDecoder setPostProcessor(@Nullable PostProcessor p) { 550 return this; 551 } 552 553 /** 554 * Set (replace) the {@link OnPartialImageListener} on this object. 555 * 556 * Will be called if there is an error in the input. Without one, a 557 * partial {@link Bitmap} will be created. 558 */ 559 public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) { 560 return this; 561 } 562 563 /** 564 * Crop the output to {@code subset} of the (possibly) scaled image. 565 * 566 * <p>{@code subset} must be contained within the size set by 567 * {@link #setResize} or the bounds of the image if setResize was not 568 * called. Otherwise an {@link IllegalStateException} will be thrown by 569 * {@link #decodeDrawable}/{@link #decodeBitmap}.</p> 570 * 571 * <p>NOT intended as a replacement for 572 * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, 573 * but merely crops the output.</p> 574 */ 575 public ImageDecoder setCrop(@Nullable Rect subset) { 576 return this; 577 } 578 579 /** 580 * Set a Rect for retrieving nine patch padding. 581 * 582 * If the image is a nine patch, this Rect will be set to the padding 583 * rectangle during decode. Otherwise it will not be modified. 584 * 585 * @hide 586 */ 587 public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) { 588 mOutPaddingRect = outPadding; 589 return this; 590 } 591 592 /** 593 * Specify whether the {@link Bitmap} should be mutable. 594 * 595 * <p>By default, a {@link Bitmap} created will be immutable, but that can 596 * be changed with this call.</p> 597 * 598 * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE}, 599 * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. 600 * Attempting to combine them will throw an 601 * {@link java.lang.IllegalStateException}.</p> 602 * 603 * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable}, 604 * which would require retrieving the Bitmap from the returned Drawable in 605 * order to modify. Attempting to decode a mutable {@link Drawable} will 606 * throw an {@link java.lang.IllegalStateException}.</p> 607 */ 608 public ImageDecoder setMutable(boolean mutable) { 609 return this; 610 } 611 612 /** 613 * Specify whether to potentially save RAM at the expense of quality. 614 * 615 * Setting this to {@code true} may result in a {@link Bitmap} with a 616 * denser {@link Bitmap.Config}, depending on the image. For example, for 617 * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config} 618 * with no alpha information. 619 */ 620 public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) { 621 return this; 622 } 623 624 /** 625 * Specify whether to potentially treat the output as an alpha mask. 626 * 627 * <p>If this is set to {@code true} and the image is encoded in a format 628 * with only one channel, treat that channel as alpha. Otherwise this call has 629 * no effect.</p> 630 * 631 * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to 632 * combine them will result in {@link #decodeDrawable}/ 633 * {@link #decodeBitmap} throwing an 634 * {@link java.lang.IllegalStateException}.</p> 635 */ 636 public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { 637 return this; 638 } 639 640 @Override 641 public void close() { 642 } 643 644 /** 645 * Create a {@link Drawable} from a {@code Source}. 646 * 647 * @param src representing the encoded image. 648 * @param listener for learning the {@link ImageInfo} and changing any 649 * default settings on the {@code ImageDecoder}. If not {@code null}, 650 * this will be called on the same thread as {@code decodeDrawable} 651 * before that method returns. 652 * @return Drawable for displaying the image. 653 * @throws IOException if {@code src} is not found, is an unsupported 654 * format, or cannot be decoded for any reason. 655 */ 656 @NonNull 657 public static Drawable decodeDrawable(@NonNull Source src, 658 @Nullable OnHeaderDecodedListener listener) throws IOException { 659 Bitmap bitmap = decodeBitmap(src, listener); 660 return new BitmapDrawable(src.getResources(), bitmap); 661 } 662 663 /** 664 * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}. 665 */ 666 @NonNull 667 public static Drawable decodeDrawable(@NonNull Source src) 668 throws IOException { 669 return decodeDrawable(src, null); 670 } 671 672 /** 673 * Create a {@link Bitmap} from a {@code Source}. 674 * 675 * @param src representing the encoded image. 676 * @param listener for learning the {@link ImageInfo} and changing any 677 * default settings on the {@code ImageDecoder}. If not {@code null}, 678 * this will be called on the same thread as {@code decodeBitmap} 679 * before that method returns. 680 * @return Bitmap containing the image. 681 * @throws IOException if {@code src} is not found, is an unsupported 682 * format, or cannot be decoded for any reason. 683 */ 684 @NonNull 685 public static Bitmap decodeBitmap(@NonNull Source src, 686 @Nullable OnHeaderDecodedListener listener) throws IOException { 687 TypedValue value = new TypedValue(); 688 value.density = src.getDensity(); 689 ImageDecoder decoder = src.createImageDecoder(); 690 if (listener != null) { 691 listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src); 692 } 693 return BitmapFactory.decodeResourceStream(src.getResources(), value, 694 ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null); 695 } 696 697 /** 698 * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. 699 */ 700 @NonNull 701 public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { 702 return decodeBitmap(src, null); 703 } 704 } 705