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