4

I'm working on a media player app and wish to load album art images to display in a ListView. Right now it works fine with the images I'm auto-downloading from last.fm which are under 500x500 png's. However, I recently added another panel to my app that allows viewing full screen artwork so I've replaced some of my artworks with large (1024x1024) png's instead.

Now when I scroll over several albums with high res artwork, I get a java.lang.OutOfMemoryError on my BitmapFactory.

    static public Bitmap getAlbumArtFromCache(String artist, String album, Context c)
    {
    Bitmap artwork = null;
    File dirfile = new File(SourceListOperations.getAlbumArtPath(c));
    dirfile.mkdirs();
    String artfilepath = SourceListOperations.getAlbumArtPath(c) + File.separator + SourceListOperations.makeFilename(artist) + "_" + SourceListOperations.makeFilename(album) + ".png";
    File infile = new File(artfilepath);
    try
    {
        artwork = BitmapFactory.decodeFile(infile.getAbsolutePath());
    }catch(Exception e){}
    if(artwork == null)
    {
        try
        {
            artwork = BitmapFactory.decodeResource(c.getResources(), R.drawable.icon);
        }catch(Exception ex){}
    }
    return artwork;
    }

Is there anything I can add to limit the size of the resulting Bitmap object to say, 256x256? That's all the bigger the thumbnails need to be and I could make a duplicate function or an argument to fetch the full size artwork for displaying full screen.

Also, I'm displaying these Bitmaps on ImageViews that are small, around 150x150 to 200x200. The smaller images scale down nicer than the large ones do. Is there any way to apply a downscaling filter to smooth the image (anti-aliasing perhaps)? I don't want to cache a bunch of additional thumbnail files if I don't have to, because it would make managing the artwork images more difficult (currently you can just dump new ones in the directory and they will automatically be used next time they get loaded).

The full code is at http://github.org/CalcProgrammer1/CalcTunes, in src/com/calcprogrammer1/calctunes/AlbumArtManager.java, though there's not much different in the other function (which falls back to checking last.fm if the image is missing).

  • Use lazy list or Universal Image Loader. Along with this http://developer.android.com/training/improving-layouts/smooth-scrolling.html. Lazy List and UIL loads a scaled down version of your bitmap. http://stackoverflow.com/questions/15621936/whats-lazylist – Raghunandan May 05 '13 at 09:56

4 Answers4

2

I use this private function to setup the size I want for my thumbnails:

//decodes image and scales it to reduce memory consumption
public static Bitmap getScaledBitmap(String path, int newSize) {
    File image = new File(path);

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    options.inInputShareable = true;
    options.inPurgeable = true;

    BitmapFactory.decodeFile(image.getPath(), options);
    if ((options.outWidth == -1) || (options.outHeight == -1))
        return null;

    int originalSize = (options.outHeight > options.outWidth) ? options.outHeight
            : options.outWidth;

    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inSampleSize = originalSize / newSize;

    Bitmap scaledBitmap = BitmapFactory.decodeFile(image.getPath(), opts);

    return scaledBitmap;     
}
Yoann Hercouet
  • 17,894
  • 5
  • 58
  • 85
0
    public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight)
{
    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(path, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(path, options);
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
return inSampleSize;
}

Adapted from http://developer.android.com/training/displaying-bitmaps/load-bitmap.html but to use loads from files rather than resources. I've added a thumbnail option as such:

    //Checks cache for album art, if it is not found return default icon
static public Bitmap getAlbumArtFromCache(String artist, String album, Context c, boolean thumb)
{
    Bitmap artwork = null;
    File dirfile = new File(SourceListOperations.getAlbumArtPath(c));
    dirfile.mkdirs();
    String artfilepath = SourceListOperations.getAlbumArtPath(c) + File.separator + SourceListOperations.makeFilename(artist) + "_" + SourceListOperations.makeFilename(album) + ".png";
    File infile = new File(artfilepath);
    try
    {
        if(thumb)
        {
            artwork = decodeSampledBitmapFromFile(infile.getAbsolutePath(), 256, 256);
        }
        else
        {
            artwork = BitmapFactory.decodeFile(infile.getAbsolutePath());
        }

Yoann's answer looks pretty similar and is a bit more condensed, might use that solution instead, but that page had some good information on it about BitmapFactory.

0

One way to do this would be the AQuery library.

This is a library that allows you to lazy load images from either your local storage or an url. With support for things like caching and downscaling.

Example to lazy load a resource without downscaling:

AQuery aq = new AQuery(mContext);
aq.id(yourImageView).image(R.drawable.myimage);

Example to lazy load a image in a File object with downscaling:

    InputStream ins = getResources().openRawResource(R.drawable.myImage);
    BufferedReader br = new BufferedReader(new InputStreamReader(ins));
    StringBuffer sb;
    String line;
    while((line = br.readLine()) != null){
        sb.append(line);
        }

    File f = new File(sb.toString());

    AQuery aq = new AQuery(mContext);
    aq.id(yourImageView).image(f,350); //Where 350 is the width to downscale to

Example how to download from an url with local memory caching, local storage caching and resizing.

AQuery aq = new AQuery(mContext);
aq.id(yourImageView).image(myImageUrl, true, true, 250, 0, null);

This will start a async download of the image at myImageUrl,resize it to a 250 width and cache it in memory and storage.Then it will show the image in your yourImageView. Whenever the image of myImageUrl has been downloaded and cached before, this line of code will load the one cached in the memory or storage instead.

Usually these methods would be called in the getView method of a list adapter.

For full documentation on AQuery's image loading capabilities, you can check the documentation.

Leon Lucardie
  • 9,541
  • 4
  • 50
  • 70
0

This is easily done with droidQuery:

final ImageView image = (ImageView) findViewById(R.id.myImage);
$.ajax(new AjaxOptions(url).type("GET")
                           .dataType("image")
                           .imageHeight(256)//set the output height
                           .imageWidth(256)//set the output width
                           .context(this)
                           .success(new Function() {
                               @Override
                               public void invoke($ droidQuery, Object... params) {
                                   $.with(image).val((Bitmap) params[0]);
                               }
                           })
                           .error(new Function() {
                               @Override
                               public void invoke($ droidQuery, Object... params) {
                                   droidQuery.toast("could not set image", Toast.LENGTH_SHORT);
                               }
                           }));

You can also cache the responses using the cache and cacheTimeout methods.

Phil
  • 35,852
  • 23
  • 123
  • 164