2011-01-27 122 views
67

我正在写卡片游戏,需要我的卡片在不同环境下的大小不同。我将图像存储为位图,以便它们可以快速绘制和重绘(用于动画)。调整大小/缩放位图后图像质量不佳

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

我已经通过这两个线程描述每一个解决方案工作:
android quality of the images resized in runtime
quality problems when resizing an image at runtime

,但仍然没有得到任何地方。

我存储我的位图(成一个HashMap)使用此代码:

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

,并用这种方法吸引他们(在卡类):

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

任何有识之士将不胜感激。谢谢

+0

当你说“这段代码完全没有区别”时,我假设你将`setAntiAlias`的参数设置为true(它仍然没有区别)? – fredley 2011-01-27 20:33:30

+0

这是正确的,我已经试验了所有这些方法的真/假,并没有任何区别 – Cbas 2011-01-27 20:35:53

回答

42

我对屏幕分辨率较低blury图像,直到位图负载我残疾人比例从资源:

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

完美的工作!清晰的图像,没有模糊。谢谢..我发现我仍然需要使用WarrenFaith的方法:过滤图像摆脱锯齿,并将inScaled选项设置为false可以消除模糊。感谢大家的帮助! – Cbas 2011-01-27 23:15:36

+0

这真的很有帮助,非常感谢你 – tejas 2012-04-17 06:35:55

+3

好东西。只需注意a.getResources()可以缩短为getResources(),path是位图的R.drawable.id。 – Shadoath 2014-03-31 15:03:02

30

createScaledBitmap有一个标志,您可以设置如果缩放图像应该过滤或不过滤。这标志提高了位图的质量...

+1

哇,这改善了很多!谢谢,我不能相信没有人在其他线索上提到过......它消除了所有尖锐的边缘,但图像仍然非常模糊。任何想法如何摆脱模糊? – Cbas 2011-01-27 20:52:04

4

如果缩放你将永远不会有一个完美的结果你的位图了。

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

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

73

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

    } 
8

用作

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG对我来说是

8

我假设你是一个Android版本低于3.2(API级别编写代码工作< 12),因为从那时起,方法的行为

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

已更改。

在较老的平台上(API级别< 12),如果BitmapFactory.decodeFile(..)方法默认情况下尝试返回带有RGB_565配置的Bitmap,前提是它们找不到任何Alpha,从而降低了内存质量。这仍然是确定的,因为你可以使用

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

真正的问题是当你的图像的每个像素为255(即完全不透明)的α值强制执行A​​RGB_8888位图。在这种情况下,Bitmap的标志'hasAlpha'被设置为false,即使您的位图具有ARGB_8888配置。如果你的* .png文件至少有一个真正的透明像素,这个标志将被设置为true,你不必担心任何事情。

所以,当你想创建一个使用

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

的方法检查“hasAlpha”标志是否被设置为真或假,并在你的情况下,它被设置为false缩放位图,这将导致获得缩放的位图,该位图自动转换为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) 这给了我们很大的力量,应该明智地使用它。我不会尝试修改最后一个字段,即使它能够工作(只是举个例子)。而且请注意,这只是一种变通方法...

这里是我实施的所有必要的方法:

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 
} 

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

3

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

5

良好缩小算法(未近邻等,所以没有像素被添加)由仅2步骤(加上用于输入/输出图像作物确切矩形的计算):

  1. 缩减使用BitmapFactory可供选项:: inSampleSize-> BitmapFactory.decodeResource()尽可能接近到你所需要的分辨率,但不低于其
  2. 得到精确的分辨率,通过缩减使用帆布:: drawBitmap一点点( )

下面是详细的解释SonyMobile如何解决这个任务:http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

这里是SonyMobile规模utils的源代码:如果你想要高品质的结果http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/

0

,所以使用[RapidDecoder] [1]图书馆。这是简单如下:

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

不要忘了,如果你想缩小小于50%和HQ结果使用内置的解码器。我在API 8上测试了它。

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; 

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

    }