vez varias una tiempo tamaño studio reducir online mismo jpg imagenes imagen fotos como cambiar aplicacion android image bitmap resize

android - varias - Problemas de calidad al cambiar el tamaño de una imagen en tiempo de ejecución



como cambiar de tamaño varias imagenes al mismo tiempo en word (7)

Tengo un archivo de imagen en el disco y estoy redimensionando el archivo y guardándolo nuevamente en el disco como un nuevo archivo de imagen. Por el bien de esta pregunta, no los traigo a la memoria para mostrarlos en la pantalla, solo para cambiarles el tamaño y volver a guardarlos. Todo esto funciona bien. Sin embargo, las imágenes escaladas tienen artefactos en ellos como se muestra aquí: android: calidad de las imágenes cambiadas de tamaño en tiempo de ejecución

Se guardan con esta distorsión, ya que puedo sacarlos del disco y mirarlos en mi computadora y todavía tienen el mismo problema.

Estoy usando un código similar a este extraño problema de memoria mientras cargaba una imagen en un objeto Bitmap para decodificar el mapa de bits en la memoria:

BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageFilePathString, options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; int scale = 1; while(srcWidth / 2 > desiredWidth){ srcWidth /= 2; srcHeight /= 2; scale *= 2; } options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = scale; Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(imageFilePathString, options);

Entonces estoy haciendo la escala real con:

Bitmap scaledBitmap = Bitmap.createScaledBitmap(sampledSrcBitmap, desiredWidth, desiredHeight, false);

Por último, la nueva imagen redimensionada se guarda en el disco con:

FileOutputStream out = new FileOutputStream(newFilePathString); scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);

Entonces, como mencioné, si retiro ese archivo del disco y lo miro, tiene ese problema de calidad vinculado anteriormente y se ve terrible. Si omito el createScaledBitmap y simplemente guardo el SampledSrcBitmap nuevamente en el disco, no hay problema, parece suceder solo si el tamaño cambia.

He intentado, como se puede ver en el código, establecer inDither en falso, como se menciona aquí http://groups.google.com/group/android-developers/browse_thread/thread/8b1abdbe881f9f71 y como se menciona en la primera publicación vinculada anterior . Eso no cambió nada. Además, en la primera publicación que vinculé, Romain Guy dijo:

En lugar de cambiar el tamaño en el momento del dibujo (que va a ser muy costoso), intente redimensionar en un mapa de bits fuera de pantalla y asegúrese de que el mapa de bits sea de 32 bits (ARGB888).

Sin embargo, no tengo idea de cómo asegurarme de que Bitmap permanezca como 32 bits durante todo el proceso.

También he leído un par de otros artículos como este http://android.nakatome.net/2010/04/bitmap-basics.html pero todos parecían abordar el dibujo y la visualización del mapa de bits, solo quiero cambiar su tamaño y guárdelo en el disco sin este problema de calidad.

Muchas gracias


"Sin embargo, no tengo idea de cómo asegurarme de que Bitmap se mantenga en 32 bits durante todo el proceso".

Quería publicar una solución alternativa, que se ocupa de mantener la configuración ARGB_8888 intacta. NOTA: Este código solo decodifica bitmaps y necesita ser extendido, por lo que podría almacenar un mapa de bits.

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:

  1. 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.

  2. 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.


Después de experimentar, finalmente encontré la manera de hacerlo con buenos resultados de calidad. Escribiré esto para cualquier persona que pueda encontrar útil esta respuesta en el futuro.

Para resolver el primer problema, los artefactos y el tramado extraño introducido en las imágenes, debe asegurarse de que su imagen permanezca como una imagen ARGB_8888 de 32 bits. Usando el código en mi pregunta, simplemente puede agregar esta línea a las opciones antes de la segunda decodificación.

options.inPreferredConfig = Bitmap.Config.ARGB_8888;

Después de agregar eso, los artefactos habían desaparecido, pero los bordes de las imágenes salían irregulares en vez de crujientes. Después de experimentar un poco más descubrí que cambiar el tamaño del mapa de bits usando una matriz en lugar de Bitmap.createScaledBitmap producía resultados mucho más nítidos.

Con esas dos soluciones, las imágenes ahora están cambiando de tamaño perfectamente. A continuación se muestra el código que estoy usando en caso de que beneficie a alguien más que se encuentre con este problema.

// Get the source image''s dimensions BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(STRING_PATH_TO_FILE, options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; // Only scale if the source is big enough. This code is just trying to fit a image into a certain width. if(desiredWidth > srcWidth) desiredWidth = srcWidth; // Calculate the correct inSampleSize/scale value. This helps reduce memory use. It should be a power of 2 // from: https://.com/questions/477572/android-strange-out-of-memory-issue/823966#823966 int inSampleSize = 1; while(srcWidth / 2 > desiredWidth){ srcWidth /= 2; srcHeight /= 2; inSampleSize *= 2; } float desiredScale = (float) desiredWidth / srcWidth; // Decode with inSampleSize options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = inSampleSize; options.inScaled = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(STRING_PATH_TO_FILE, options); // Resize Matrix matrix = new Matrix(); matrix.postScale(desiredScale, desiredScale); Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0, sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true); sampledSrcBitmap = null; // Save FileOutputStream out = new FileOutputStream(NEW_FILE_PATH); scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); scaledBitmap = null;

EDITAR: después del trabajo continuo en esto, he encontrado que las imágenes todavía no son 100% perfectas. Haré una actualización si puedo mejorarlo.

Actualización: después de revisar esto, encontré esta pregunta en SO y había una respuesta que mencionaba la opción inScaled. Esto también ayudó con la calidad, así que agregué la respuesta actualizada para incluirla. También ahora anulo los bitmaps después de que terminan de ser utilizados.

Además, como nota al margen, si usa estas imágenes en una WebView, asegúrese de tener en cuenta esta publicación.

Nota: también debe agregar un cheque para asegurarse de que el ancho y el alto sean números válidos (no -1). Si lo son, provocará que el ciclo inSampleSize se vuelva infinito.


En mi situación, estoy dibujando la imagen en la pantalla. Esto es lo que hice para que mis imágenes se vean correctas (una combinación de la respuesta de LittleFluffyKitty, además de algunas otras).

Para mis opciones cuando realmente cargo la imagen (usando decodeResource) establezco los siguientes valores:

options.inScaled = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888;

Cuando dibujo la imagen, configuré mi objeto de pintura así:

Paint paint = new Paint(); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true);

Ojalá alguien más también lo encuentre útil. Ojalá hubiera solo opciones para "Sí, deje que mis imágenes redimensionadas parezcan basura" y "No, no obligue a mis usuarios a sacar sus cucharillas con los ojos" en lugar de a la gran cantidad de opciones diferentes. Sé que quieren darnos mucho control, pero tal vez algunos métodos de ayuda para configuraciones comunes podrían ser útiles.


La escala de la imagen también se puede lograr de esta manera sin pérdida de calidad.

//Bitmap bmp passed to method... ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream); Image jpg = Image.getInstance(stream.toByteArray()); jpg.scalePercent(68); // or any other number of useful methods.


Por lo tanto, createScaledBitmap y createBitmap (con matriz que se escala) en mapa de bits inmutables (como cuando se decodifica) ignorará Bitmap.Config original y creará bitmap con Bitmap.Config.ARGB_565 si el original no tiene ninguna transparencia (hasAlpha == false). Pero no lo hará en mapa de bits mutable. Por lo tanto, si su mapa de bits decodificado es b:

Bitmap temp = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(temp); canvas.drawBitmap(b, 0, 0, null); b.recycle();

Ahora puede volver a escalar la temperatura y debería conservar Bitmap.Config.ARGB_8888.


littleFluffyKitty una biblioteca simple basada en la respuesta littleFluffyKitty que littleFluffyKitty tamaño y hace otras cosas como recortar y girar, así que por favor Android-ImageResizer para usarla y mejorarla: Android-ImageResizer .


onScreenResults = Bitmap.createScaledBitmap(tempBitmap, scaledOSRW, scaledOSRH, true); <----

configurar el filtro como verdadero funcionó para mí.