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

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

我的问题是,无论我如何尝试和缩放我的图像(无论是通过matrix.postScale,matrix.preScale或createScaledBitmap函数),他们总是出现像素化和模糊。 我知道它的缩放导致的问题,因为图像看起来完美绘制时不resize。

我已经完成了这两个线程中描述的每个解决scheme:
在运行时调整图像的android质量
在运行时调整图像的质量问题

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

我用这个代码存储我的位图(到一个hashmap):

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

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

我在低分辨率屏幕上使用了blury图像,直到我禁用从资源上的位图加载缩放:

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

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

createScaledBitmap有一个标志,你可以设置是否缩放图像应该被过滤。 该标志提高了位图的质量…

用于

 mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG是为我工作

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

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

已经改变。

在较旧的平台(API级别<12),BitmapFactory.decodeFile(..)方法默认情况下会尝试返回一个带有RGB_565configuration的位图,如果它们找不到任何alpha,这会降低iamge的质量。 这仍然可以,因为您可以使用强制ARGB_8888位图

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

真正的问题出现在图像的每个像素都具有255的alpha值(即完全不透明)的时候。 在这种情况下,Bitmap的标志'hasAlpha'被设置为false,即使你的位图有ARGB_8888configuration。 如果你的* .png文件至less有一个真正的透明像素,这个标志将被设置为true,你不必担心任何事情。

所以当你想创build一个缩放的位图使用

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

该方法检查'hasAlpha'标志是否设置为true或false,并在你的情况下它被设置为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 (eg 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); } 

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

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

  2. 使用JNI为给定的Bitmap对象调用“setHasAlpha”方法。 这完美的工作,即使是私人的方法或领域。 JNI并没有检查你是否违反访问控制规则。 资料来源: http : //java.sun.com/docs/books/jni/html/pitfalls.html (10.9)这给了我们很大的力量,应该明智地使用它。 我不会尝试修改最后一个字段,即使它能工作(只是举个例子)。 请注意这只是一个解决方法…

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

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

加载你的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 } 

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

好的缩小algorithm(不是最近邻居,所以不添加像素)只包含2个步骤(加上计算input/输出图像裁剪的精确矩形):

  1. 使用BitmapFactory.Options :: inSampleSize-> BitmapFactory.decodeResource()尽可能接近你需要的分辨率,但不能低于它的分辨率
  2. 通过使用Canvas :: drawBitmap()缩小一点来获得准确的分辨率

以下是SonyMobile如何解决此任务的详细说明: http : //developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

以下是SonyMobile scale utils的源代码: http : //developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/

如果您缩放位图,则永远不会有完美的结果。

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

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

我只是在bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);使用了flag filter=true bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); 为模糊。

如果你想要高质量的结果,那么使用[RapidDecoder] [1]库。 简单如下:

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

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

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