web-dev-qa-db-ja.com

ビットマップのサイズ変更/スケーリング後の画質が悪い

私はカードゲームを書いていますが、カードは異なる状況で異なるサイズである必要があります。イメージをビットマップとして保存し、それらをすばやく描画および再描画できるようにします(アニメーション用)。

私の問題は、(matrix.postScale、matrix.preScale、またはcreateScaledBitmap関数を使用して)画像をどのように試行およびスケーリングしても、常にピクセル化されてぼやけてしまうことです。サイズ変更せずに描画すると画像が完璧に見えるので、スケーリングが問題の原因であることを知っています。

これらの2つのスレッドで説明されているすべてのソリューションに取り組んできました。
実行時にサイズ変更された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);
}

どんな洞察も大歓迎です。ありがとう

75
Cbas

リソースからのビットマップの読み込みのスケーリングを無効にするまで、低画面解像度でぼやけた画像がありました。

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

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;

    }
81
le huynh

createScaledBitmapには、スケーリングされたイメージをフィルタリングするかどうかを設定できるフラグがあります。そのフラグはビットマップの品質を改善します...

33
WarrenFaith

使用

mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 

Paint.FILTER_BITMAP_FLAGは私の仕事です

10
chuckwu

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構成のビットマップを返そうとします。アルファが見つからない場合、画像の品質が低下します。これを使用して、ARGB_8888ビットマップを強制的に使用できるため、これでも大丈夫です。

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

実際の問題は、画像の各ピクセルのアルファ値が255(つまり完全に不透明)の場合に発生します。その場合、ビットマップにARGB_8888構成が含まれていても、ビットマップのフラグ「hasAlpha」はfalseに設定されます。 * .pngファイルに少なくとも1つの実際の透明ピクセルがある場合、このフラグは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を使​​用して、特定のビットマップオブジェクトに対して「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;
    }

ライブラリをロードし、ネイティブメソッドを宣言します。

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
}

それでおしまい。できました。コピーアンドペーストの目的でコード全体を投稿しました。実際のコードはそれほど大きくはありませんが、これらの偏執的なエラーチェックをすべて行うと、コードが大きくなります。これが誰にとっても役立つことを願っています。

8
Ivo

優れたダウンスケーリングアルゴリズム(最近傍ではないため、ピクセル化は追加されません)は、わずか2ステップで構成されます(さらに、入力/出力画像クロップの正確なRectの計算):

  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 /

7
goRGon

ビットマップを拡大すると、完璧な結果が得られることはありません。

必要な最高の解像度から始めて、縮小する必要があります。

ビットマップを拡大する場合、拡大縮小では既存の各ポイント間の欠落ポイントを推測できないため、近隣を複製する(=>エッジの効いた)か、近隣間の平均値を計算する(=>ぼやける)。

4
Kevin Gaudin

bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);filter=trueフラグをぼかしに使用しました。

3

高品質の結果が必要な場合は、[RapidDecoder] [1]ライブラリを使用してください。次のように簡単です:

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

50%未満に縮小してHQの結果を得る場合は、組み込みデコーダーを使用することを忘れないでください。 API 8でテストしました。

1
S.M.Mousavi

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フレームワークの下で開発しています

1
Aldous Realo
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;

    }
0
Gil SH