android - 在android中解码流时保持图像质量

标签 android image android-layout bitmap android-largeheap

我在 sdcard 上有一张图片,需要在图片 View 中显示

问题是解码后,质量似乎变差了。有什么方法既能保持质量又能保留内存?

或者,如果我使用较大的图像,是否有任何方法可以通过缩放来保留内存(避免加载太大的位图)? (我需要保持原图的大小)

感谢您的帮助。

public Bitmap decodeFile(String pubKey, int bookPageID, int type)
        throws IOException {
    Bitmap b = null;
    File f = null;
    String uri = null;
    FileInputStream fis = null;

    Log.d(TAG,"pageID to read: " + bookPageID);

    IRIssue issue = Broker.model.issueDataStore.getIRIssue(pubKey);

    String imageFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey);

    // pageID - 1 since the page is an array (start at 0) , but page ID start at 1
    if (type == 2){
        uri = imageFolder + issue.vol[0].pages[bookPageID - 1].graphicUri;
    }else {
        uri = imageFolder + issue.vol[0].pages[bookPageID - 1].textUri;
    }

    f = new File(uri);

    Log.d(TAG,"is file: " + uri + " exist?" + f.exists());

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPurgeable = true;
    options.inInputShareable = true;
    options.inJustDecodeBounds = false;
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, options);
    fis.close();

    return b;
}

最佳答案

以下代码使用了 Displaying Bitmaps Efficiently 中的几个概念
首先,位图读取是在后台线程中完成的,我在 inputStream 上使用标记/重置(用 BufferedInputstream 包装),以便在我们从流中读取不必要的内容时尝试找出计算比例因子时要使用的图像大小。下面的示例代码对图像进行子采样以匹配 320x240 像素的大小。在非示例代码中,可以使用简单的回调接口(interface)将位图从 onPostExecute 发送到实现类(回调接口(interface)实现者)。或者直接将 View 作为 AsyncTask 的成员提供,并在 onPostExecute 中设置位图。

调用代码(例如在我的设备上下载的图像):

BitmapTask task = new BitmapTask(getContentResolver());
task.execute(Uri.parse("file:///storage/emulated/0/Download/download.jpg"));

有问题的类

private static class BitmapTask extends AsyncTask<Uri, Void, Bitmap> {

    // prevent mem leaks
    private WeakReference<ContentResolver> mWeakContentResolver;

    public BitmapTask(ContentResolver resolver) {
        mWeakContentResolver = new WeakReference<ContentResolver>(resolver);
    }

    @Override
    protected Bitmap doInBackground(Uri... params) {
        Bitmap bitmap = null;
        ContentResolver resolver = mWeakContentResolver.get();
        if (resolver != null) {
            BufferedInputStream stream = null;
            try {
                stream = new BufferedInputStream(
                        resolver.openInputStream(params[0]));
                stream.mark(1 * 1024);
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                // Find out size of image
                BitmapFactory.decodeStream(stream, null, options);
                try {
                    stream.reset();
                } catch (IOException e) {
                    Log.d(TAG, "reset failed");
                }
                int imageHeight = options.outHeight;
                int imageWidth = options.outWidth;
                String imageType = options.outMimeType;
                Log.d(TAG, "w, h, mime " + imageWidth + " , " + imageHeight
                        + " , " + imageType);
                options.inJustDecodeBounds = false;
                // Calculate down scale factor
                options.inSampleSize = calculateInSampleSize(options, 320,
                        240);
                return BitmapFactory.decodeStream(stream, null, options);
            } catch (FileNotFoundException e) {
                bitmap = null;
            } finally {
                IOUtils.closeStreamSilently(stream);
            }
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        Log.d(TAG,
                "bitmap result: "
                        + ((result != null) ? "" + result.getByteCount()
                                : "0"));
        result.recycle();
    }
}

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) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and
        // keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

编辑:对于大型输入流,标记/重置技术可能存在问题,SkImageDecoder::Factory returned null 有时可以在日志中看到,导致 null位图,关于此事的其他 SO 问题:SkImageDecoder::Factory returned null .可以通过再次重新初始化流变量来修复 stream = new resolver.openInputStream(params[0]));doInBackground

返回之前

编辑 2:如果您必须保留图像大小但又不想限制内存使用,您可以使用 options.inPreferredConfig = Bitmap.Config.RGB_565;这会将每个像素的内存减半,但请记住,图像可能不再具有很好的质量(实验!)。

关于android - 在android中解码流时保持图像质量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20664655/

相关文章:

android - Android 上的 Applaud & Phonegap

Android - 检查手机是否有网络提供商

html - 使用 background-image over img 属性的响应图像

javascript - Android4.1和iOS环境下的window.open()

python - 计算图像中不同颜色的面积比

css - 我的头像哪里去了?带有 flexbox 的背景图片

android - 如图所示的水平 ScrollView

Android:做很多 Activity 更好,还是做 1 个动态变化的 Activity ?

android - 将 <include> 标签与 ?attr/myAttr 一起使用

java - 安卓服务停止