android - trata - video viral de la moto
Mala calidad de imagen después de redimensionar/escalar mapa de bits (10)
Estoy escribiendo un juego de cartas y necesito que mis cartas sean de diferentes tamaños en diferentes circunstancias. Estoy almacenando mis imágenes como bitmaps para que puedan dibujarse y volverse a dibujar rápidamente (para la animación).
Mi problema es que no importa cómo intente escalar mis imágenes (ya sea a través de una matrix.postScale, una matrix.preScale, o una función createScaledBitmap) siempre salen pixeladas y borrosas. Sé que es la escala la que causa el problema porque las imágenes se ven perfectas cuando se dibujan sin cambiar el tamaño.
He trabajado en todas las soluciones descritas en estos dos hilos:
Calidad de Android de las imágenes redimensionada en tiempo de ejecución
problemas de calidad al cambiar el tamaño de una imagen en tiempo de ejecución
pero aún no han llegado a ninguna parte.
Guardo mis bitmaps (en un hashmap) con este código:
cardImages = new HashMap<Byte, Bitmap>();
cardImages.put(GameUtil.hearts_ace, BitmapFactory.decodeResource(r, R.drawable.hearts_ace));
y dibujarlos con este método (en una clase de Tarjeta):
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);
}
Cualquier idea sería muy apreciada. Gracias
Acabo de usar flag filter=true
en bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
para desenfoque.
Nunca obtendrá un resultado perfecto si escala sus bitmaps.
Debe comenzar con la resolución más alta que necesita y reducirla.
Al escalar un mapa de bits, la escala no puede adivinar cuáles son los puntos que faltan entre cada punto existente, por lo que duplica un vecino (=> nervioso) o calcula un valor medio entre vecinos (=> borroso).
Si desea un resultado de alta calidad, utilice la biblioteca [RapidDecoder] [1]. Es simple como sigue:
import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
.scale(width, height)
.useBuiltInDecoder(true)
.decode();
No te olvides de usar el decodificador incorporado si deseas reducir la escala a menos del 50% y un resultado HQ. Lo probé en API 8.
Supongo que está escribiendo código para una versión de Android inferior a 3.2 (nivel de API <12), porque desde entonces el comportamiento de los métodos
BitmapFactory.decodeFile(pathToImage);
BitmapFactory.decodeFile(pathToImage, opt);
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
ha cambiado.
En las plataformas más antiguas (nivel API <12), los métodos BitmapFactory.decodeFile (..) intentan devolver un mapa de bits con la configuración RGB_565 de forma predeterminada, si no pueden encontrar ningún alfa, lo que reduce la calidad de un iamge. Esto todavía está bien, porque puede aplicar un mapa de bits ARGB_8888 usando
options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false
El verdadero problema surge cuando cada píxel de su imagen tiene un valor alfa de 255 (es decir, completamente opaco). En ese caso, el indicador de mapa de bits ''hasAlpha'' se establece en falso, aunque su Bitmap tenga la configuración ARGB_8888. Si su * .png-file tenía al menos un píxel real transparente, esta bandera se habría establecido en verdadero y no tendría que preocuparse por nada.
Entonces, cuando quiera crear un mapa de bits escalado usando
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
el método verifica si el indicador ''hasAlpha'' se establece en verdadero o falso, y en su caso se establece en falso, lo que da como resultado la obtención de un mapa de bits escalado, que se convirtió automáticamente al formato RGB_565.
Por lo tanto, en el nivel de API> = 12 hay un método público llamado
public void setHasAlpha (boolean hasAlpha);
que habría resuelto este problema. Hasta ahora, esto fue solo una explicación del problema. Investigué un poco y me di cuenta de que el método setHasAlpha existe desde hace mucho tiempo y es público, pero ha estado oculto (@hide annotation). Así es como se define en 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);
}
Ahora aquí está mi propuesta de solución. No implica ninguna copia de datos de mapa de bits:
Comprobado en tiempo de ejecución usando java.lang.Reflect si la implementación actual de Bitmap tiene un método público ''setHasAplha''. (De acuerdo con mis pruebas, funciona perfectamente desde API nivel 3, y no he probado versiones inferiores, porque JNI no funcionaría). Puede tener problemas si un fabricante explícitamente lo ha hecho privado, protegido o eliminado.
Llame al método ''setHasAlpha'' para un objeto Bitmap determinado usando JNI. Esto funciona perfectamente, incluso para métodos o campos privados. Es oficial que JNI no verifica si está violando las reglas de control de acceso o no. Fuente: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) Esto nos da un gran poder, que debe usarse con prudencia. No trataría de modificar un campo final, incluso si funcionaría (solo para dar un ejemplo). Y tenga en cuenta que esto es solo una solución ...
Aquí está mi implementación de todos los métodos necesarios:
PARTE DE 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;
}
Cargue su lib y declare el método nativo:
static {
System.loadLibrary("bitmaputils");
}
private static native int setHasAlphaNative(Bitmap bitmap, boolean value);
Sección nativa (carpeta ''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
}
Eso es. Hemos terminado. Publiqué todo el código para copiar y pegar. El código real no es tan grande, pero hacer todos estos controles de error paranoicos lo hace mucho más grande. Espero que esto pueda ser útil para cualquiera.
Tenía imágenes blury en resoluciones de pantalla baja hasta que desactivé la ampliación de la carga de mapa de bits de los recursos:
Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);
Un buen algoritmo de reducción de escala (no como el vecino más cercano, por lo que no se agrega pixelación) consta de solo 2 pasos (más el cálculo del Rect exacto para el cultivo de imágenes de entrada / salida):
- downscale utilizando BitmapFactory.Options :: inSampleSize-> BitmapFactory.decodeResource () lo más cerca posible de la resolución que necesita, pero no menos que
- obtener la resolución exacta reduciendo la escala un poco usando Canvas :: drawBitmap ()
Aquí hay una explicación detallada de cómo SonyMobile resolvió esta tarea: http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Aquí está el código fuente de utilidades de escala de SonyMobile: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/
Use createScaledBitmap hará que su imagen se vea muy mal. He encontrado este problema y lo he resuelto. El siguiente código solucionará el problema:
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;
}
usar como
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
Paint.FILTER_BITMAP_FLAG
es el trabajo para mí
createScaledBitmap
tiene un indicador donde puede configurar si la imagen escalada debe ser filtrada o no. Esa bandera mejora la calidad del mapa de bits ...
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;
}