13713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/* 23713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Copyright (C) 2011 The Android Open Source Project 33713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 43713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Licensed under the Apache License, Version 2.0 (the "License"); 53713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * you may not use this file except in compliance with the License. 63713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * You may obtain a copy of the License at 73713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 83713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * http://www.apache.org/licenses/LICENSE-2.0 93713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Unless required by applicable law or agreed to in writing, software 113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * distributed under the License is distributed on an "AS IS" BASIS, 123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * See the License for the specific language governing permissions and 143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * limitations under the License. 153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpackage com.android.volley.toolbox; 183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.DefaultRetryPolicy; 203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.NetworkResponse; 213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.ParseError; 223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.Request; 233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.Response; 243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.VolleyLog; 253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport android.graphics.Bitmap; 273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport android.graphics.Bitmap.Config; 283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport android.graphics.BitmapFactory; 293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/** 313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * A canned request for getting an image at a given URL and calling 323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * back with a decoded Bitmap. 333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpublic class ImageRequest extends Request<Bitmap> { 353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** Socket timeout in milliseconds for image requests */ 363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private static final int IMAGE_TIMEOUT_MS = 1000; 373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** Default number of retries for image requests */ 393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private static final int IMAGE_MAX_RETRIES = 2; 403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** Default backoff multiplier for image requests */ 423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private static final float IMAGE_BACKOFF_MULT = 2f; 433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private final Response.Listener<Bitmap> mListener; 453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private final Config mDecodeConfig; 463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private final int mMaxWidth; 473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private final int mMaxHeight; 483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */ 503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private static final Object sDecodeLock = new Object(); 513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Creates a new image request, decoding to a maximum specified width and 543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * height. If both width and height are zero, the image will be decoded to 553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * its natural size. If one of the two is nonzero, that dimension will be 563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * clamped and the other one will be set to preserve the image's aspect 573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * ratio. If both width and height are nonzero, the image will be decoded to 583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * be fit in the rectangle of dimensions width x height while keeping its 593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * aspect ratio. 603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param url URL of the image 623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param listener Listener to receive the decoded bitmap 633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param maxWidth Maximum width to decode this bitmap to, or zero for none 643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param maxHeight Maximum height to decode this bitmap to, or zero for 653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * none 663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param decodeConfig Format to decode the bitmap to 673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param errorListener Error listener, or null to ignore errors 683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, 703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick Config decodeConfig, Response.ErrorListener errorListener) { 713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick super(url, errorListener); 723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick setRetryPolicy( 733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT)); 743713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick mListener = listener; 753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick mDecodeConfig = decodeConfig; 763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick mMaxWidth = maxWidth; 773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick mMaxHeight = maxHeight; 783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick @Override 813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick public Priority getPriority() { 823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return Priority.LOW; 833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Scales one side of a rectangle to fit aspect ratio. 873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param maxPrimary Maximum size of the primary dimension (i.e. width for 893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * max width), or zero to maintain aspect ratio with secondary 903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * dimension 913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param maxSecondary Maximum size of the secondary dimension, or zero to 923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * maintain aspect ratio with primary dimension 933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param actualPrimary Actual size of the primary dimension 943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param actualSecondary Actual size of the secondary dimension 953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, 973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int actualSecondary) { 983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // If no dominant value at all, just return the actual. 993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (maxPrimary == 0 && maxSecondary == 0) { 1003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return actualPrimary; 1013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // If primary is unspecified, scale primary to match secondary's scaling ratio. 1043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (maxPrimary == 0) { 1053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick double ratio = (double) maxSecondary / (double) actualSecondary; 1063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return (int) (actualPrimary * ratio); 1073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (maxSecondary == 0) { 1103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return maxPrimary; 1113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick double ratio = (double) actualSecondary / (double) actualPrimary; 1143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int resized = maxPrimary; 1153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (resized * ratio > maxSecondary) { 1163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick resized = (int) (maxSecondary / ratio); 1173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return resized; 1193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick @Override 1223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) { 1233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Serialize all decode on a global lock to reduce concurrent heap usage. 1243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick synchronized (sDecodeLock) { 1253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick try { 1263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return doParse(response); 1273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } catch (OutOfMemoryError e) { 1283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl()); 1293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return Response.error(new ParseError(e)); 1303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 1353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * The real guts of parseNetworkResponse. Broken out for readability. 1363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 1373713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick private Response<Bitmap> doParse(NetworkResponse response) { 1383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick byte[] data = response.data; 1393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); 1403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick Bitmap bitmap = null; 1413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (mMaxWidth == 0 && mMaxHeight == 0) { 1423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick decodeOptions.inPreferredConfig = mDecodeConfig; 1433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 1443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } else { 1453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // If we have to resize this image, first get the natural bounds. 1463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick decodeOptions.inJustDecodeBounds = true; 1473713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 1483713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int actualWidth = decodeOptions.outWidth; 1493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int actualHeight = decodeOptions.outHeight; 1503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Then compute the dimensions we would ideally like to decode to. 1523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, 1533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick actualWidth, actualHeight); 1543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, 1553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick actualHeight, actualWidth); 1563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Decode to the nearest power of two scaling factor. 1583713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick decodeOptions.inJustDecodeBounds = false; 1593713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it? 1603713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; 1613713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick decodeOptions.inSampleSize = 1623713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); 1633713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick Bitmap tempBitmap = 1643713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 1653713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1663713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Then scale to the exact desired size, if necessary. 1673713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (tempBitmap != null && (tempBitmap.getWidth() != desiredWidth || 1683713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick tempBitmap.getHeight() != desiredHeight)) { 1693713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick bitmap = Bitmap.createScaledBitmap(tempBitmap, 1703713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick desiredWidth, desiredHeight, true); 1713713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick tempBitmap.recycle(); 1723713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } else { 1733713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick bitmap = tempBitmap; 1743713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1753713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1763713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (bitmap == null) { 1783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return Response.error(new ParseError()); 1793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } else { 1803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); 1813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1843713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick @Override 1853713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick protected void deliverResponse(Bitmap response) { 1863713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick mListener.onResponse(response); 1873713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1883713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 1903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Returns the largest power-of-two divisor for use in downscaling a bitmap 1913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * that will not result in the scaling past the desired dimensions. 1923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 1933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param actualWidth Actual width of the bitmap 1943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param actualHeight Actual height of the bitmap 1953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param desiredWidth Desired width of the bitmap 1963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param desiredHeight Desired height of the bitmap 1973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 1983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Visible for testing. 1993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick static int findBestSampleSize( 2003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { 2013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick double wr = (double) actualWidth / desiredWidth; 2023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick double hr = (double) actualHeight / desiredHeight; 2033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick double ratio = Math.min(wr, hr); 2043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick float n = 1.0f; 2053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick while ((n * 2) <= ratio) { 2063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick n *= 2; 2073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 2083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 2093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return (int) n; 2103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 2113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick} 212