Плохое качество изображения после изменения размера или масштабирования растрового изображения
Я пишу карточную игру и мне нужно, чтобы мои карты были разных размеров в разных обстоятельствах. Я сохраняю свои изображения в виде растровых изображений, чтобы их можно было быстро рисовать и перерисовывать (для анимации).
моя проблема заключается в том, что независимо от того, как я пытаюсь масштабировать свои изображения (будь то через матрицу.postScale, матрицы.preScale или функция createScaledBitmap) они всегда выходят неровными и размытыми. Я знаю, что его масштабирование, которое вызывает проблему, потому что изображения выглядят идеально при рисовании без изменения размера.
Я проработал каждое решение, описанное в этих двух потоках:
android качество изображений, измененных во время выполнения
проблемы качества при изменении размера изображения во время выполнения
но все равно ничего не получилось.
Я храню мои растровые изображения (в 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);
}
любой проницательность была бы весьма признательна. Спасибо
10 ответов:
У меня были размытые изображения с низким разрешением экрана, пока я не отключил масштабирование при загрузке растрового изображения из ресурсов:
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
BitmapFactory.decodeFile(pathToImage); BitmapFactory.decodeFile(pathToImage, opt); bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
изменилось.
на старых платформах (уровень API
options.inPrefferedConfig = Bitmap.Config.ARGB_8888 options.inDither = false
настоящая проблема возникает, когда каждый пиксель изображения имеет Альфа-значение 255 (т. е. непрозрачная). В этом случае флаг растрового изображения "hasAlpha" имеет значение false, даже если ваше растровое изображение имеет конфигурацию ARGB_8888. Если ваш *.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); }
теперь вот мое предложение по решению. Он не включает в себя копирование растровых данных:
проверено во время выполнения с помощью java.ленг.Отразить, если текущий Реализация растрового изображения имеет открытый метод "setHasAplha". (Согласно моим тестам, он отлично работает с уровня API 3, и я не тестировал более низкие версии, потому что JNI не будет работать). У вас могут быть проблемы, если производитель явно сделал его частным, защищенным или удалил его.
вызовите метод 'setHasAlpha' для данного растрового объекта с помощью JNI. Это прекрасно работает, даже для частных методов или полей. Официально 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')
Андроид.МК:
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 }
вот и все. Мы закончили. Я отправил сообщение весь код для копирования и вставки целей. Фактический код не так уж велик, но все эти параноидальные проверки ошибок делают его намного больше. Я надеюсь, что это может быть полезно для всех.
хороший алгоритм масштабирования (не ближайший сосед, как, так что пикселизация не добавляется) состоит всего из 2 шагов (плюс расчет точного Ректа для ввода / вывода изображения урожая):
- вниз по шкале с помощью BitmapFactory.Параметры:: inSampleSize - > BitmapFactory.decodeResource() как можно ближе к решению, что вам нужно, но не менее чем
- получить точное разрешение путем уменьшения масштаба немного с помощью 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 scale utils: https://web.archive.org/web/20170105181810/http://developer.sonymobile.com:80/downloads/code-example-module/image-scaling-code-example-for-android/
У вас никогда не будет идеального результата, если вы масштабируете свои растровые изображения.
вы должны начать с самого высокого разрешения вам нужно и уменьшить масштаб.
при масштабировании растрового изображения вверх масштабирование не может угадать, какие недостающие точки между каждой существующей точкой, поэтому он либо дублирует сосед (=>резкий), либо вычисляет среднее значение между соседями (=>размытый).
Я просто использовал флаг
filter=true
inbitmap = 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.
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; }