调整大小/缩放位图后图像质量不佳

时间:2011-01-27 20:29:38

标签: android image-manipulation

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

我的问题是,无论我如何尝试和缩放我的图像(无论是通过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);
}

非常感谢任何见解。感谢

11 个答案:

答案 0 :(得分:79)

使用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;

    }

答案 1 :(得分:45)

我在低屏幕分辨率下出现模糊图像,直到我从资源禁用了位图加载缩放:

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

答案 2 :(得分:33)

createScaledBitmap有一个标志,您可以设置是否应过滤缩放后的图像。那个标志改善了位图的质量......

答案 3 :(得分:10)

用作

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG对我有用

答案 4 :(得分:8)

我假设您正在为低于3.2的Android版本(API级别&lt; 12)编写代码,因为从那时起方法的行为

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

已经改变。

在较旧的平台(API级别&lt; 12)上,BitmapFactory.decodeFile(..)方法在默认情况下尝试返回带有RGB_565配置的Bitmap,如果它们找不到任何alpha,这会降低iamge的质量。这仍然可以,因为您可以使用

强制执行A​​RGB_8888位图
options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false 

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

因此,当您想使用

创建缩放的位图时
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);

该方法检查'hasAlpha'标志是否设置为true或false,并且在您的情况下将其设置为false,这将导致获得缩放的Bitmap,该Bitmap自动转换为RGB_565格式。

因此,在API级别&gt; = 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为给定的Bitmap对象调用'setHasAlpha'方法。 即使对于私有方法或字段,这也可以完美地工作。官方认为JNI不会检查您是否违反了访问控制规则。 资料来源:http://java.sun.com/docs/books/jni/html/pitfalls.html(10.9) 这给了我们很大的力量,应该明智地使用。我不会尝试修改最终字段,即使它可以工作(只是举个例子)。请注意,这只是一种解决方法......

  3. 以下是我对所有必要方法的实现:

    JAVA PART:

    // 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;
        }
    

    加载lib并声明本机方法:

    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
    }
    

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

答案 5 :(得分:7)

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

  1. 使用 BitmapFactory.Options :: inSampleSize - &gt;缩减。 BitmapFactory.decodeResource() 尽可能接近您需要但不低于它的分辨率
  2. 通过使用 Canvas :: drawBitmap()
  3. 缩小一点来获得精确的分辨率

    以下是SonyMobile如何解决此任务的详细说明: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/

答案 6 :(得分:4)

如果扩展位图,将无法获得完美的结果。

您应该从所需的最高分辨率开始并缩小。

当向上缩放位图时,缩放无法猜测每个现有点之间的缺失点,因此它要么复制邻居(=&gt; edgy),要么计算邻居之间的平均值(=&gt;模糊)

答案 7 :(得分:3)

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

答案 8 :(得分:1)

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

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

如果您希望缩小小于50%和HQ结果,请不要忘记使用内置解码器。我在API 8上进行了测试。

答案 9 :(得分:0)

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;

    }

答案 10 :(得分:0)

在将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框架下进行开发