chromium/ui/android/java/src/org/chromium/ui/resources/statics/StaticResource.java

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.resources.statics;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.ui.resources.Resource;
import org.chromium.ui.resources.ResourceFactory;

/**
 * A representation of a static resource and all related information for drawing it.  In general
 * this means a {@link Bitmap} and a potential {@link NinePatchData}.
 */
public class StaticResource implements Resource {
    private Bitmap mBitmap;
    private final NinePatchData mNinePatchData;
    private final Rect mBitmapSize;

    /**
     * Creates a {@link StaticResource} that represents {@code bitmap}.  This will automatically
     * pull out the {@link NinePatchData} from {@code bitmap} if it exists.
     * @param bitmap The {@link Bitmap} to build a {@link StaticResource} of.
     */
    public StaticResource(Bitmap bitmap) {
        mBitmap = bitmap;
        mNinePatchData = NinePatchData.create(mBitmap);
        mBitmapSize = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
    }

    @Override
    public NinePatchData getNinePatchData() {
        return mNinePatchData;
    }

    @Override
    public Bitmap getBitmap() {
        assert mBitmap != null : "StaticResource#getBitmap can only be called once per lifecycle";
        Bitmap bitmap = mBitmap;
        mBitmap = null;
        return bitmap;
    }

    @Override
    public boolean shouldRemoveResourceOnNullBitmap() {
        return false;
    }

    @Override
    public Rect getBitmapSize() {
        return mBitmapSize;
    }

    @Override
    public long createNativeResource() {
        return ResourceFactory.createBitmapResource(mNinePatchData);
    }

    /**
     * Attempts to load the Android resource specified by {@code resId} from {@code resources}. This
     * will attempt to first load the resource as a {@code Bitmap}. If that fails it will try to
     * load the resource as a {@link Drawable}.
     *
     * @param resources The {@link Resources} instance to load from.
     * @param resId The id of the Android resource to load.
     * @param fitWidth The smallest width the image can be. The image will be shrunk to scale to try
     *     to get close to this value. Or use {@code 0} to use the intrinsic size.
     * @param fitHeight The smallest height the image can be. The image will be shrunk to scale to
     *     try to get close to this value. Or use {@code 0} to use the intrinsic size.
     * @return The loaded {@link StaticResource} or {@code null} if the resource could not be
     *     loaded.
     */
    public static StaticResource create(
            Resources resources, int resId, int fitWidth, int fitHeight) {
        if (resId <= 0) return null;
        Bitmap bitmap = decodeBitmap(resources, resId, fitWidth, fitHeight);
        if (bitmap == null) bitmap = decodeDrawable(resources, resId, fitWidth, fitHeight);
        if (bitmap == null) return null;

        return new StaticResource(bitmap);
    }

    private static Bitmap decodeBitmap(
            Resources resources, int resId, int fitWidth, int fitHeight) {
        BitmapFactory.Options options = createOptions(resources, resId, fitWidth, fitHeight);
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        Bitmap bitmap = BitmapFactory.decodeResource(resources, resId, options);

        if (bitmap == null) return null;
        if (bitmap.getConfig() == options.inPreferredConfig) return bitmap;

        Bitmap convertedBitmap =
                Bitmap.createBitmap(
                        bitmap.getWidth(), bitmap.getHeight(), options.inPreferredConfig);
        Canvas canvas = new Canvas(convertedBitmap);
        canvas.drawBitmap(bitmap, 0, 0, null);
        bitmap.recycle();
        return convertedBitmap;
    }

    private static Bitmap decodeDrawable(
            Resources resources, int resId, int fitWidth, int fitHeight) {
        try {
            Drawable drawable = ApiCompatibilityUtils.getDrawable(resources, resId);
            int width = Math.max(drawable.getMinimumWidth(), Math.max(fitWidth, 1));
            int height = Math.max(drawable.getMinimumHeight(), Math.max(fitHeight, 1));
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, width, height);
            drawable.draw(canvas);
            return bitmap;
        } catch (Resources.NotFoundException ex) {
            return null;
        }
    }

    private static BitmapFactory.Options createOptions(
            Resources resources, int resId, int fitWidth, int fitHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        if (fitWidth == 0 || fitHeight == 0) return options;

        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, resId, options);
        options.inJustDecodeBounds = false;

        if (options.outHeight <= fitHeight && options.outWidth <= fitWidth) return options;

        int heightRatio = Math.round((float) options.outHeight / (float) fitHeight);
        int widthRatio = Math.round((float) options.outWidth / (float) fitWidth);
        options.inSampleSize = Math.min(heightRatio, widthRatio);

        return options;
    }
}