【问题标题】:Bad image quality after resizing/scaling bitmap调整大小/缩放位图后图像质量不佳
【发布时间】:2011-06-16 19:53:17
【问题描述】:

我正在编写一款纸牌游戏,并且需要我的卡片在不同情况下具有不同的尺寸。我将图像存储为位图,以便可以快速绘制和重绘(用于动画)。

我的问题是,无论我如何尝试缩放图像(无论是通过 matrix.postScale、matrix.preScale 还是 createScaledBitmap 函数),它们总是会出现像素化和模糊。我知道这是导致问题的缩放比例,因为在不调整大小的情况下绘制图像时看起来很完美。

我已经完成了这两个线程中描述的每个解决方案:
android quality of the images resized in runtime
quality problems when resizing an image at runtime

但仍然没有到达任何地方。

我使用以下代码存储我的位图(到哈希图中):

cardImages = new HashMap<Byte, Bitmap>();
cardImages.put(GameUtil.hearts_ace, BitmapFactory.decodeResource(r, R.drawable.hearts_ace));

并使用此方法(在 Card 类中)绘制它们:

public void drawCard(Canvas c)
{
    //retrieve the cards image (if it doesn't already have one)
    if (image == null)
        image = Bitmap.createScaledBitmap(GameUtil.cardImages.get(ID), 
            (int)(GameUtil.standardCardSize.X*scale), (int)(GameUtil.standardCardSize.Y*scale), false);

        //this code (non-scaled) looks perfect
        //image = GameUtil.cardImages.get(ID);

    matrix.reset();
    matrix.setTranslate(position.X, position.Y);

    //These methods make it look worse
    //matrix.preScale(1.3f, 1.3f);
    //matrix.postScale(1.3f, 1.3f);

    //This code makes absolutely no difference
    Paint drawPaint = new Paint();
    drawPaint.setAntiAlias(false);
    drawPaint.setFilterBitmap(false);
    drawPaint.setDither(true);

    c.drawBitmap(image, matrix, drawPaint);
}

任何见解将不胜感激。谢谢

【问题讨论】:

  • 当您说“此代码完全没有区别”时,我假设您将 setAntiAlias 的参数设置为 true(仍然没有区别)?
  • 没错,我已经对所有这些方法进行了真/假试验,但它们都没有任何区别
  • 这个问题已经在Meta SE提到过

标签: android image-manipulation


【解决方案1】:

使用 createScaledBitmap 会使您的图像看起来很糟糕。 我遇到了这个问题,我已经解决了。 下面的代码将解决问题:

public Bitmap BITMAP_RESIZER(Bitmap bitmap,int newWidth,int newHeight) {    
    Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);

    float ratioX = newWidth / (float) bitmap.getWidth();
    float ratioY = newHeight / (float) bitmap.getHeight();
    float middleX = newWidth / 2.0f;
    float middleY = newHeight / 2.0f;

    Matrix scaleMatrix = new Matrix();
    scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);

    Canvas canvas = new Canvas(scaledBitmap);
    canvas.setMatrix(scaleMatrix);
    canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));

    return scaledBitmap;

    }

【讨论】:

  • 太棒了!你真是个天才!非常感谢:D
  • 这个解决方案是完美的(它是唯一对我有用的),但是你可以摆脱 middleXmiddleY :只需输入 0 并声明 canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
  • 我曾经用不同于 Paint.FILTER_BITMAP_FLAGS 的颜料绘制位图。更改为 Paint.FILTER_BITMAP_FLAG 极大地改善了我的结果!谢谢!
  • 结果有很大改善,但有时我在相机捕获图像后尝试使用此方法时方法不起作用..
  • 很抱歉,但这个解决方案不适用于我的情况。
【解决方案2】:

在我禁用从资源加载位图时的缩放功能之前,我在低屏幕分辨率下的图像很模糊:

Options options = new BitmapFactory.Options();
    options.inScaled = false;
    Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);

【讨论】:

  • 效果很好!清晰的图像,没有模糊。谢谢.. 我发现我仍然需要使用 WarrenFaith 的方法:过滤图像以消除锯齿状,并将 inScaled 选项设置为 false 以消除模糊。感谢大家的帮助!
  • 好东西。请注意,a.getResources() 可以缩短为 getResources(),path 是位图的 R.drawable.id。
  • 效果很好。我评论了这一行 o2.inSampleSize=scale;并根据您的建议添加此行( o2.inScaled = false; )。谢谢。但是我还是将比例值设置为1。那么为什么会出现问题,请您详细说明。
【解决方案3】:

createScaledBitmap 有一个标志,您可以在其中设置是否应过滤缩放的图像。该标志提高了位图的质量...

【讨论】:

  • 哇,这改进了很多!谢谢,我不敢相信没有人在其他线程上提到过……它消除了所有锐利的边缘,但图像仍然非常模糊。关于如何摆脱模糊的任何想法?
【解决方案4】:

用作

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG 适合我

【讨论】:

  • 不知道为什么这被否决了,这是我的解决方案。
  • 谢谢,这拯救了我的一天!我用它作为 canvas.drawBitmap 方法的参数
【解决方案5】:

我假设您正在为低于 3.2(API 级别

BitmapFactory.decodeFile(pathToImage);
BitmapFactory.decodeFile(pathToImage, opt);
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);

变了。

在旧平台(API 级别 强制执行 ARGB_8888 位图

options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false 

真正的问题出现在图像的每个像素的 alpha 值为 255(即完全不透明)时。在这种情况下,即使您的位图具有 ARGB_8888 配置,位图的标志“hasAlpha”也会设置为 false。如果您的 *.png 文件至少有一个真正的透明像素,则该标志将设置为 true,您不必担心任何事情。

所以当你想创建一个缩放位图时使用

bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);

该方法检查“hasAlpha”标志是设置为真还是假,在您的情况下,它设置为假,这会导致获得一个缩放的位图,该位图会自动转换为 RGB_565 格式。

因此在 API 级别 >= 12 上有一个名为的公共方法

public void setHasAlpha (boolean hasAlpha);

本来可以解决这个问题的。到目前为止,这只是对问题的解释。 我做了一些研究,发现 setHasAlpha 方法已经存在了很长时间并且它是公开的,但是已经被隐藏了(@hide 注释)。以下是它在 Android 2.3 上的定义:

/**
 * Tell the bitmap if all of the pixels are known to be opaque (false)
 * or if some of the pixels may contain non-opaque alpha values (true).
 * Note, for some configs (e.g. RGB_565) this call is ignore, since it does
 * not support per-pixel alpha values.
 *
 * This is meant as a drawing hint, as in some cases a bitmap that is known
 * to be opaque can take a faster drawing case than one that may have
 * non-opaque per-pixel alpha values.
 *
 * @hide
 */
public void setHasAlpha(boolean hasAlpha) {
    nativeSetHasAlpha(mNativeBitmap, hasAlpha);
}

现在这是我的解决方案建议。它不涉及任何位图数据的复制:

  1. 在运行时使用 java.lang.Reflect 进行检查,如果当前 位图实现有一个公共的“setHasAplha”方法。 (根据我的测试,它从 API 级别 3 开始就可以完美运行,而且我还没有测试过较低版本,因为 JNI 不起作用)。如果制造商明确将其设为私有、保护或删除,您可能会遇到问题。

  2. 使用 JNI 为给定的位图对象调用“setHasAlpha”方法。 即使对于私有方法或字段,这也很有效。 JNI 不检查您是否违反访问控制规则是官方的。 来源:http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) 这给了我们强大的力量,应该明智地使用它。我不会尝试修改 final 字段,即使它会起作用(仅举个例子)。请注意,这只是一种解决方法...

这是我对所有必要方法的实现:

JAVA 部分:

// NOTE: this cannot be used in switch statements
    private static final boolean SETHASALPHA_EXISTS = setHasAlphaExists();

    private static boolean setHasAlphaExists() {
        // get all puplic Methods of the class Bitmap
        java.lang.reflect.Method[] methods = Bitmap.class.getMethods();
        // search for a method called 'setHasAlpha'
        for(int i=0; i<methods.length; i++) {
            if(methods[i].getName().contains("setHasAlpha")) {
                Log.i(TAG, "method setHasAlpha was found");
                return true;
            }
        }
        Log.i(TAG, "couldn't find method setHasAlpha");
        return false;
    }

    private static void setHasAlpha(Bitmap bitmap, boolean value) {
        if(bitmap.hasAlpha() == value) {
            Log.i(TAG, "bitmap.hasAlpha() == value -> do nothing");
            return;
        }

        if(!SETHASALPHA_EXISTS) {   // if we can't find it then API level MUST be lower than 12
            // couldn't find the setHasAlpha-method
            // <-- provide alternative here...
            return;
        }

        // using android.os.Build.VERSION.SDK to support API level 3 and above
        // use android.os.Build.VERSION.SDK_INT to support API level 4 and above
        if(Integer.valueOf(android.os.Build.VERSION.SDK) <= 11) {
            Log.i(TAG, "BEFORE: bitmap.hasAlpha() == " + bitmap.hasAlpha());
            Log.i(TAG, "trying to set hasAplha to true");
            int result = setHasAlphaNative(bitmap, value);
            Log.i(TAG, "AFTER: bitmap.hasAlpha() == " + bitmap.hasAlpha());

            if(result == -1) {
                Log.e(TAG, "Unable to access bitmap."); // usually due to a bug in the own code
                return;
            }
        } else {    //API level >= 12
            bitmap.setHasAlpha(true);
        }
    }

    /**
     * Decodes a Bitmap from the SD card
     * and scales it if necessary
     */
    public Bitmap decodeBitmapFromFile(String pathToImage, int pixels_limit) {
        Bitmap bitmap;

        Options opt = new Options();
        opt.inDither = false;   //important
        opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
        bitmap = BitmapFactory.decodeFile(pathToImage, opt);

        if(bitmap == null) {
            Log.e(TAG, "unable to decode bitmap");
            return null;
        }

        setHasAlpha(bitmap, true);  // if necessary

        int numOfPixels = bitmap.getWidth() * bitmap.getHeight();

        if(numOfPixels > pixels_limit) {    //image needs to be scaled down 
            // ensures that the scaled image uses the maximum of the pixel_limit while keeping the original aspect ratio
            // i use: private static final int pixels_limit = 1280*960; //1,3 Megapixel
            imageScaleFactor = Math.sqrt((double) pixels_limit / (double) numOfPixels);
            Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,
                    (int) (imageScaleFactor * bitmap.getWidth()), (int) (imageScaleFactor * bitmap.getHeight()), false);

            bitmap.recycle();
            bitmap = scaledBitmap;

            Log.i(TAG, "scaled bitmap config: " + bitmap.getConfig().toString());
            Log.i(TAG, "pixels_limit = " + pixels_limit);
            Log.i(TAG, "scaled_numOfpixels = " + scaledBitmap.getWidth()*scaledBitmap.getHeight());

            setHasAlpha(bitmap, true); // if necessary
        }

        return bitmap;
    }

加载你的库并声明本地方法:

static {
    System.loadLibrary("bitmaputils");
}

private static native int setHasAlphaNative(Bitmap bitmap, boolean value);

本机部分('jni' 文件夹)

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := bitmaputils
LOCAL_SRC_FILES := bitmap_utils.c
LOCAL_LDLIBS := -llog -ljnigraphics -lz -ldl -lgcc
include $(BUILD_SHARED_LIBRARY)

bitmapUtils.c:

#include <jni.h>
#include <android/bitmap.h>
#include <android/log.h>

#define  LOG_TAG    "BitmapTest"
#define  Log_i(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  Log_e(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


// caching class and method IDs for a faster subsequent access
static jclass bitmap_class = 0;
static jmethodID setHasAlphaMethodID = 0;

jint Java_com_example_bitmaptest_MainActivity_setHasAlphaNative(JNIEnv * env, jclass clazz, jobject bitmap, jboolean value) {
    AndroidBitmapInfo info;
    void* pixels;


    if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
        Log_e("Failed to get Bitmap info");
        return -1;
    }

    if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        Log_e("Incompatible Bitmap format");
        return -1;
    }

    if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
        Log_e("Failed to lock the pixels of the Bitmap");
        return -1;
    }


    // get class
    if(bitmap_class == NULL) {  //initializing jclass
        // NOTE: The class Bitmap exists since API level 1, so it just must be found.
        bitmap_class = (*env)->GetObjectClass(env, bitmap);
        if(bitmap_class == NULL) {
            Log_e("bitmap_class == NULL");
            return -2;
        }
    }

    // get methodID
    if(setHasAlphaMethodID == NULL) { //initializing jmethodID
        // NOTE: If this fails, because the method could not be found the App will crash.
        // But we only call this part of the code if the method was found using java.lang.Reflect
        setHasAlphaMethodID = (*env)->GetMethodID(env, bitmap_class, "setHasAlpha", "(Z)V");
        if(setHasAlphaMethodID == NULL) {
            Log_e("methodID == NULL");
            return -2;
        }
    }

    // call java instance method
    (*env)->CallVoidMethod(env, bitmap, setHasAlphaMethodID, value);

    // if an exception was thrown we could handle it here
    if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
        Log_e("calling setHasAlpha threw an exception");
        return -2;
    }

    if(AndroidBitmap_unlockPixels(env, bitmap) < 0) {
        Log_e("Failed to unlock the pixels of the Bitmap");
        return -1;
    }

    return 0;   // success
}

就是这样。我们完了。我已经发布了整个代码用于复制和粘贴目的。 实际的代码并没有那么大,但是进行所有这些偏执的错误检查会使它变得更大。我希望这对任何人都有帮助。

【讨论】:

  • 你能在GitHub的一个示例android项目中添加上述代码吗?你的方法很有趣。
  • 抱歉,我没有本地开发的设置了。
【解决方案6】:

良好的缩小算法(不是最近邻,因此不添加像素化)仅包含 2 个步骤(加上计算输入/输出图像裁剪的精确 Rect):

  1. 使用 BitmapFactory.Options::inSampleSize -> BitmapFactory.decodeResource() 尽可能接近您需要的分辨率,但不能低于它
  2. 使用 Canvas::drawBitmap() 稍微缩小一点以达到精确的分辨率

这里是索尼移动如何解决这个任务的详细解释:https://web.archive.org/web/20171011183652/http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

这里是 SonyMobile scale utils 的源代码: https://web.archive.org/web/20170105181810/http://developer.sonymobile.com:80/downloads/code-example-module/image-scaling-code-example-for-android/

【讨论】:

    【解决方案7】:

    如果你放大你的位图,你永远不会得到完美的结果。

    您应该从所需的最高分辨率开始,然后按比例缩小。

    当放大位图时,缩放无法猜测每个现有点之间的缺失点是什么,因此它要么复制相邻点 (=> edgy),要么计算相邻点之间的平均值 (=> blurry)。

    【讨论】:

    • 这完全有道理,尽管使用上述方法,我已经能够获得非常清晰的图像 - 清晰到我看不出真实图像和放大后的图像之间有任何区别.我确信它并不完美,但它看起来确实很像(我正在使用最新的 Android 模型之一来测试它)。虽然有效点。如果我想在平板电脑上发布这款游戏,我肯定会考虑这一点
    【解决方案8】:

    我刚刚使用了标志filter=true bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); 用于模糊。

    【讨论】:

      【解决方案9】:

      如果您想要高质量的结果,请使用 [RapidDecoder][1] 库。很简单,如下:

      import rapid.decoder.BitmapDecoder;
      ...
      Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
                                   .scale(width, height)
                                   .useBuiltInDecoder(true)
                                   .decode();
      

      如果您想缩小小于 50% 并获得 HQ 结果,请不要忘记使用内置解码器。我在 API 8 上对其进行了测试。

      【讨论】:

        【解决方案10】:

        在将 Android Target Framework 从 Android 8.1 更新到 Android 9 并在我的 ImageEntryRenderer 上显示时出现此问题。希望这会有所帮助

            public Bitmap ProcessScaleBitMap(Bitmap bitmap, int newWidth, int newHeight)
            {
                newWidth = newWidth * 2;
                newHeight = newHeight * 2;
        
                Bitmap scaledBitmap = CreateBitmap(newWidth, newHeight, Config.Argb8888);
        
                float scaleDensity = ((float)Resources.DisplayMetrics.DensityDpi / 160);
                float scaleX = newWidth / (bitmap.Width * scaleDensity);
                float scaleY = newHeight / (bitmap.Height * scaleDensity);
        
                Matrix scaleMatrix = new Matrix();
                scaleMatrix.SetScale(scaleX, scaleY);
        
                Canvas canvas = new Canvas(scaledBitmap);
                canvas.Matrix = scaleMatrix;
                canvas.DrawBitmap(bitmap, 0, 0, new Paint(PaintFlags.FilterBitmap));
        
                return scaledBitmap;
            }
        

        注意:我是在 Xamarin 3.4.0.10 框架下开发

        【讨论】:

          【解决方案11】:
          private static Bitmap createScaledBitmap(Bitmap bitmap,int newWidth,int newHeight) {
                  Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig());
          
                  float scaleX = newWidth / (float) bitmap.getWidth();
                  float scaleY = newHeight / (float) bitmap.getHeight();
          
                  Matrix scaleMatrix = new Matrix();
                  scaleMatrix.setScale(scaleX, scaleY, 0, 0);
          
                  Canvas canvas = new Canvas(scaledBitmap);
                  canvas.setMatrix(scaleMatrix);
                  Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
                  paint.setAntiAlias(true);
                  paint.setDither(true);
                  paint.setFilterBitmap(true);
                  canvas.drawBitmap(bitmap, 0, 0, paint);
          
                  return scaledBitmap;
          
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-03-04
            • 2014-05-04
            • 1970-01-01
            • 2013-02-15
            • 2020-08-20
            相关资源
            最近更新 更多