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

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

我的问题是,无论我如何尝试和缩放我的图像(无论是通过一个 Matrix. postScale,一个 Matrix. preScale,或一个 createScaledBitmap 函数) ,它们总是像素化和模糊。我知道是缩放造成了这个问题,因为在不调整大小的情况下绘制的图像看起来非常完美。

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

但还是没有进展。

我用下面的代码存储我的位图(到散列表中) :

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

如果你能提供点线索,我将不胜感激,谢谢

107013 次浏览

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

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

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;


}

用作

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);

Paint.FILTER_BITMAP_FLAG是我的工作

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

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

已经变了。

在较老的平台(API 级别 < 12)上,BitmapFactory.decdeFile (。.)如果找不到 alpha 值,则默认情况下返回带有 RGB _ 565配置的 Bitmap,这会降低图像的质量。这仍然可以,因为您可以使用

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

当图像的每个像素的 alpha 值为255(即完全不透明)时,真正的问题就出现了。在这种情况下,Bitmap 的标志“ hasAlpha”被设置为 false,即使您的 Bitmap 具有 ARGB _ 8888配置。如果你的 * 。Png-file 至少有一个真正的透明像素,这个标志将被设置为真,你不必担心任何事情。

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

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 (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 检查 位图实现有一个公共的“ setHasAplha”方法。 (根据我的测试,它从 API 级别3开始就完美工作了,我还没有测试过更低的版本,因为 JNI 不能工作)。如果制造商明确地将其私有化、保护或删除,您可能会遇到问题。

  2. 使用 JNI 调用给定 Bitmap 对象的“ setHasAlpha”方法。 即使对于私有方法或字段,这也可以很好地工作。JNI 没有检查您是否违反了访问控制规则,这是正式的。 资料来源: http://java.sun.com/docs/books/jni/html/pitfalls.html(10.9) 这给了我们巨大的力量,我们应该明智地使用它。我不会尝试修改 final 字段,即使它会起作用(只是举个例子)。请注意,这只是一个变通方案

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

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

加载库并声明本机方法:

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


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

原生节(‘ jni’文件夹)

安卓:

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
}

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

我刚用了标志 filter=true bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); 为了侠影。

好的缩放算法(不像最近邻那样,所以不添加像素化)只包括两个步骤(加上输入/输出图像作物的精确 Rect 计算) :

  1. 降低使用 < em > BitmapFactory. Options: : inSampleSize-> BitmapFactory.decdeResource () 的分辨率尽可能接近你需要的,但不低于它
  2. 通过使用 < em > Canvas: : draBitmap () 缩放一点来获得精确的分辨率

下面是 SonyMobile 如何解决这个问题的详细说明: https://web.archive.org/web/20171011183652/http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

下面是 SonyMobile Scale 工具的源代码: Https://web.archive.org/web/20170105181810/http://developer.sonymobile.com:80/downloads/code-example-module/image-scaling-code-example-for-android/

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

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

不要忘记使用内置解码器,如果你想减少少于50% 和总部的结果。我在 API 8上测试过。

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;


}

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