调整位图大小/缩放后图像质量较差

发布于 2024-10-14 15:31:19 字数 1612 浏览 2 评论 0原文

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

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

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

I'm writing a card game and need my cards to be different sizes in different circumstances. I am storing my images as bitmaps so that they can be quickly drawn and redrawn (for animation).

My problem is that no matter how I try and scale my images (whether through a matrix.postScale, a matrix.preScale, or a createScaledBitmap function) they always come out pixelated and blurry. I know that its the scaling thats causing the problem because the images look perfect when drawn without resizing.

I have worked through every solution described in these two threads:
android quality of the images resized in runtime
quality problems when resizing an image at runtime

but still haven't gotten anywhere.

I store my bitmaps (into a hashmap) with this code:

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

and draw them with this method (in a Card class):

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

Any insight would be greatly appreciated. Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

拍不死你 2024-10-21 15:31:19

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

    }

Use createScaledBitmap will make your image looks very bad.
I've met this problem and I've resolved it.
Below code will fix the problem:

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;

    }
风筝有风,海豚有海 2024-10-21 15:31:19

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

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

I had blury images on low screen resolutions until I disabled scaling on bitmap load from resources:

Options options = new BitmapFactory.Options();
    options.inScaled = false;
    Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);
浮萍、无处依 2024-10-21 15:31:19

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

createScaledBitmap has a flag where you can set if the scaled image should be filtered or not. That flag improves the quality of the bitmap...

聚集的泪 2024-10-21 15:31:19

用作

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG 对我来说很有效

use as

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAG is work for me

倾城月光淡如水﹏ 2024-10-21 15:31:19

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

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

已经发生了变化。

在较旧的平台(API 级别 < 12)上,如果找不到任何 alpha,BitmapFactory.decodeFile(..) 方法默认会尝试返回具有 RGB_565 配置的位图,这会降低图像的质量。这仍然没问题,因为您可以使用强制执行 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”标志是否设置为 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.Reflect 检查当前是否
    位图实现有一个公共的“setHasAplha”方法。
    (根据我的测试,它从 API 级别 3 开始就完美工作,并且我没有测试较低版本,因为 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;
    }

加载您的库并声明本机方法:

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

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

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

Android.mk:bitmapUtils.c

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)

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

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

I assume you are writing code for a version of Android lower than 3.2 (API level < 12), because since then the behavior of the methods

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

has changed.

On older platforms (API level < 12) the BitmapFactory.decodeFile(..) methods try to return a Bitmap with RGB_565 config by default, if they can't find any alpha, which lowers the quality of an iamge. This is still ok, because you can enforce an ARGB_8888 bitmap using

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

The real problem comes when each pixel of your image has an alpha value of 255 (i.e. completely opaque). In that case the Bitmap's flag 'hasAlpha' is set to false, even though your Bitmap has ARGB_8888 config. If your *.png-file had at least one real transparent pixel, this flag would have been set to true and you wouldn't have to worry about anything.

So when you want to create a scaled Bitmap using

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

the method checks whether the 'hasAlpha' flag is set to true or false, and in your case it is set to false, which results in obtaining a scaled Bitmap, which was automatically converted to the RGB_565 format.

Therefore on API level >= 12 there is a public method called

public void setHasAlpha (boolean hasAlpha);

which would have solved this issue. So far this was just an explanation of the problem.
I did some research and noticed that the setHasAlpha method has existed for a long time and it's public, but has been hidden (@hide annotation). Here is how it is defined on 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);
}

Now here is my solution proposal. It does not involve any copying of bitmap data:

  1. Checked at runtime using java.lang.Reflect if the current
    Bitmap implementation has a public 'setHasAplha' method.
    (According to my tests it works perfectly since API level 3, and i haven't tested lower versions, because JNI wouldn't work). You may have problems if a manufacturer has explicitly made it private, protected or deleted it.

  2. Call the 'setHasAlpha' method for a given Bitmap object using JNI.
    This works perfectly, even for private methods or fields. It is official that JNI does not check whether you are violating the access control rules or not.
    Source: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9)
    This gives us great power, which should be used wisely. I wouldn't try modifying a final field, even if it would work (just to give an example). And please note this is just a workaround...

Here is my implementation of all necessary methods:

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

Load your lib and declare the native method:

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

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

Native section ('jni' folder)

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
}

That's it. We are done. I've posted the whole code for copy-and-paste purposes.
The actual code isn't that big, but making all these paranoid error checks makes it a lot bigger. I hope this could be helpful to anyone.

我不在是我 2024-10-21 15:31:19

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

  1. 使用 BitmapFactory.Options::inSampleSize 缩小 - > BitmapFactory.decodeResource() 尽可能接近您需要的分辨率,但不少于
  2. 使用 Canvas::drawBitmap( )

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

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

Good downscaling algorithm (not nearest neighbor like, so no pixelation is added) consists of just 2 steps (plus calculation of the exact Rect for input/output images crop):

  1. downscale using BitmapFactory.Options::inSampleSize -> BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
  2. get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()

Here is detailed explanation how SonyMobile resolved this task: https://web.archive.org/web/20171011183652/http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/

Here is the source code of SonyMobile scale utils:
https://web.archive.org/web/20170105181810/http://developer.sonymobile.com:80/downloads/code-example-module/image-scaling-code-example-for-android/

尐籹人 2024-10-21 15:31:19

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

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

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

You will never have a perfect result if you scale your bitmaps up.

You should start with the highest resolution you need and scale down.

When scaling a bitmap up, the scaling can't guess what are the missing points between each existing point, so it either duplicates a neighbour (=> edgy) or calculates a mean value between neighbours (=> blurry).

江湖正好 2024-10-21 15:31:19

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

I just used flag filter=true in
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
for blur.

尹雨沫 2024-10-21 15:31:19

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

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

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

If you want high quality result, so use [RapidDecoder][1] library. It is simple as follow:

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

Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result. I tested it on API 8.

卸妝后依然美 2024-10-21 15:31:19

将 Android 目标框架从 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 框架下开发

Had this issue upon updating Android Target Framework from Android 8.1 to Android 9 and manifested on my ImageEntryRenderer. Hope this helps

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

Note: I am developing under Xamarin 3.4.0.10 framework

野稚 2024-10-21 15:31:19
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;

    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文