Tengo un archivo de imagen en el disco y estoy cambiar el tamaño del archivo y se vuelve a guardar en el disco como un nuevo archivo de imagen. Por el bien de esta pregunta, no estoy trayendo a la memoria con el fin de mostrar en la pantalla, sólo para cambiar el tamaño de ellos y guardarlos. Todo esto funciona bien. Sin embargo, la escala de las imágenes han artefactos en ellos, como se muestra aquí: android: la calidad de las imágenes de tamaño en tiempo de ejecución

Que se guardan con esta distorsión, como puedo sacar el disco y buscar en ellos en mi equipo y ellos todavía tienen el mismo problema.

Estoy usando un código similar a este Extraño fuera de la memoria problema, mientras que la carga de una imagen a un objeto de mapa de bits para descifrar 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 yo estoy haciendo el real de la escala con el:

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

Como mencioné, si puedo sacar ese archivo en el disco y mira, se tiene que el problema de calidad enlazado más arriba y se ve terrible. Si me salto la createScaledBitmap y guardar sólo el sampledSrcBitmap a la derecha de nuevo en el disco no hay ningún problema, parece que sólo sucederá si los cambios de tamaño.

Que he probado, como se puede ver en el código, configuración de inDither a falso como se menciona aquí http://groups.google.com/group/android-developers/browse_thread/thread/8b1abdbe881f9f71 y como se menciona en el primer post vinculado anteriormente. Eso no cambia nada. También, en el primer post he enlazado, Romain Guy dijo:

Lugar de cambio de tamaño en tiempo de dibujo
(que va a ser muy costoso),
intente cambiar el tamaño en un fuera de la pantalla de mapa de bits
y asegúrese de que el mapa de bits de 32 bits
(ARGB888).

Sin embargo, no tengo idea de cómo asegurarse de que las estancias de mapa de bits como de 32 bits a través de todo el proceso.

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

Gracias

  • Estás cambio de tamaño de imágenes para resolver problemas con diferentes densidades de pantalla? Si es así, supongo que tendría que ejecutar este código al inicio y no se inicia posteriores… O simplemente ejecutando localmente para obtener nuevos activos (es decir, imágenes) para las pequeñas pantallas? Tengo curiosidad porque me estoy quedando en los errores de pequeña densidad de pantallas.
  • En mi caso no es realmente acerca de la densidad de pantalla. Sólo estoy tratando de adaptarse a un determinado conjunto de imágenes en un determinado recipiente.

7 Comentarios

  1. 54

    Después de experimentar por fin he encontrado una manera de hacer esto con buenos resultados de calidad. Voy a escribir esto para cualquier persona que podría encontrar esta respuesta útil en el futuro.

    Para resolver el primer problema, los artefactos y extraño tramado introducido en las imágenes, usted necesita para asegurarse de que su imagen se mantiene como una de 32 bits ARGB_8888 de la imagen. Utilizando el código de mi pregunta, usted puede simplemente añadir esta línea a las opciones antes de la segunda decodificar.

    options.inPreferredConfig = Bitmap.Config.ARGB_8888;

    Después de la adición de que los artefactos se han ido, pero a lo largo de los bordes de las imágenes llegaron a través de los irregulares en vez de crujiente. Después de algo más de experimentación descubrí que el tamaño del mapa de bits mediante una Matriz en lugar de mapa de bits.createScaledBitmap producido mucho más nítidas resultados.

    Con esas dos soluciones, las imágenes son ahora cambio de tamaño de la perfección. A continuación está el código que estoy utilizando en el caso de que se beneficia a alguien venir a través de 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://stackoverflow.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;

    EDIT: Después de que el trabajo continuo en este que he encontrado que las imágenes aún no son 100% perfectos. Voy a hacer una actualización si puedo mejorarlo.

    Actualización: Después de revisting esto, he encontrado esta cuestión, ASÍ y hubo una respuesta que menciona el inScaled opción. Esto ayudó con la calidad así lo he añadido la actualización de la respuesta de arriba para incluirlo. Yo ahora también nulos los mapas de bits después de haber realizado el que se utiliza.

    También, como una nota de lado, si usted está usando estas imágenes en un WebView, asegúrese de tomar este post en consideración.

    Nota: usted también debe agregar una comprobación para asegurarse de que la anchura y la altura son válidos los números (no -1). Si lo son, esto hará que la inSampleSize bucle para convertirse en infinito.

    • comprobar esta respuesta si desea ARGB_8888 calidad: stackoverflow.com/questions/3440690/…
    • Esto funcionó para mí, gracias! Mucho mejor miniatura de los resultados. Es posible que desee eliminar su srcHeight variable, como es ins no se usa.
    • Esto funcionó muy bien, pero el único problema es cuando la imagen cambia de tamaño está en posición vertical, después de cambiar el tamaño se convierte en jardines. Alguna idea de cómo evitar eso?
  2. 8

    En mi situación estoy dibujando la imagen a la pantalla. Aquí es lo que hice para conseguir mis imágenes para la correcta (una combinación de littleFluffyKitty la respuesta, además de algunas otras cosas).

    De mis opciones cuando en realidad me carga la imagen (usando decodeResource) he creado los siguientes valores:

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

    Cuando realmente se dibuja la imagen, puedo configurar mi pintura objeto como este:

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

    Esperemos que alguien más se encuentra que útil. Me gustaría que hubiera sólo opciones de «Sí, vamos a mi cambiar de tamaño las imágenes se ven como basura» y «No, por favor No fuerza a mis usuarios para esquilmar sus ojos con cucharas» en lugar de toda la miríada de diferentes opciones. Sé que nos quiere dar una gran cantidad de control, pero tal vez algunos métodos auxiliares para la configuración común podría ser útil.

    • Funciona como un encanto.Gracias 🙂 Usted salvó mi día
  3. 3

    He creado sencillo biblioteca basada en la littleFluffyKitty respuesta que hace cambiar el tamaño y la hace un poco de otras cosas como los cultivos y la rotación, así que por favor libre de usarlo y mejorarlo – Android-ImageResizer.

  4. 2

    «Sin embargo, no tengo idea de cómo asegurarse de que las estancias de mapa de bits como de 32 bits
    a través de todo el proceso.»

    Quería publicar una solución alternativa, que se encarga de mantener la ARGB_8888 config virgen. NOTA: Este código sólo decodifica los mapas de bits y necesita ser ampliado, por lo que se puede almacenar en un mapa de bits.

    Supongo que usted está escribiendo código para la versión de Android inferior a 3.2 (API level < 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 anteriores (API level < 12) la BitmapFactory.decodeFile(..) los métodos de intentar devolver un mapa de bits con RGB_565 config por defecto, si no se puede encontrar ningún alfa, lo que disminuye la calidad de una pantalla. Esto está bien, porque puede hacer que un ARGB_8888 de mapa de bits utilizando

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

    El verdadero problema viene cuando cada píxel de la imagen tiene un valor de alfa de 255 (es decir, completamente opaco). En ese caso, el mapa de bits de la bandera de la ‘hasAlpha’ se establece en false, incluso a pesar de que el mapa de bits tiene ARGB_8888 config. Si su *.png-archivo tenido al menos un pixel transparente, este indicador se han puesto a true y usted no tiene que preocuparse de nada.

    Así que cuando usted desea crear una escala de mapa de bits mediante

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

    el método comprueba si el ‘hasAlpha’ indicador se establece en true o false, y en su caso se establece en false, lo que resulta en la obtención de una escala de mapa de bits, que se ha convertido automáticamente en el RGB_565 formato.

    Por lo tanto en el nivel de la API >= 12 no es un método público llamado

    public void setHasAlpha (boolean hasAlpha);

    que habría resuelto este problema. Hasta ahora esto era sólo una explicación del problema.
    Hice algunas investigaciones y noté que el setHasAlpha método ha existido durante mucho tiempo y es público, pero se ha ocultado (@ocultar anotación). Aquí es cómo 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 mapa de bits de datos:

    1. Comprueba en tiempo de ejecución usando java.lang.Reflejar si el actual
      Mapa de bits de la aplicación tiene un público ‘setHasAplha’ método.
      (Según mis pruebas, funciona a la perfección ya que la API de nivel 3, y no he probado versiones inferiores, porque JNI no iba a funcionar). Usted puede tener problemas si un fabricante ha hecho expresamente se private, protected o eliminado.

    2. Llamada la ‘setHasAlpha’ método para un determinado objeto de mapa de bits usando JNI.
      Esto funciona perfectamente, incluso los métodos privados o los campos. Es oficial que JNI no comprobar si se están 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 ser utilizado con prudencia. Yo no trate de modificar un campo final, incluso si iba a funcionar (sólo para dar un ejemplo). Y tenga en cuenta que esto es sólo una solución…

    Aquí está mi aplicación de todos los métodos necesarios:

    JAVA PARTE:

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

    De carga de su lib y declarar el método nativo:

    static {
    System.loadLibrary("bitmaputils");
    }
    private static native int setHasAlphaNative(Bitmap bitmap, boolean value);

    Nativo de la sección (‘jni’ carpeta)

    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
    }

    Que es. Hemos terminado. He puesto todo el código para copiar y pegar los efectos.
    El código real no es grande, pero haciendo todas estas paranoico comprobaciones de errores hace que sea mucho más grande. Espero que esto podría ser útil para cualquier persona.

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

    configurar el filtro true trabajó para mí.

    • Mismo aquí, el cambio de la bandera de false a true solucionado mi problema.
  6. 0

    Así, createScaledBitmap y createBitmap (con la matriz que las escalas) en inmutable de mapa de bits (como cuando se decodifica) ignorará mapa de bits original.Config y crear mapa de bits con mapa de bits.Config.ARGB_565 si el original no tiene ningún tipo de transparencia (hasAlpha == false).
    Pero no lo va a hacer en mutable de mapa de bits.
    Por lo tanto, si su decodificado de mapa de bits 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 usted puede cambiar la escala de temperatura y debe retener de mapa de bits.Config.ARGB_8888.

  7. 0

    De escala de la imagen también se puede lograr por este medio sin ninguna 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.
    • ¿Cuál es la Imagen de la clase? Yo no soy consciente de que una clase de ese nombre. Gracias
    • La programación orientada a objetos – de mi mal, me han indicado que es parte de la droidText o iText fuente/biblioteca. Yo lo uso también para guardar mis imágenes en pdf. La clase de Imagen ofrece una serie de métodos útiles. Tal vez el que droidText es openSource puede extraer únicamente la clase de Imagen para la manipulación de Imágenes. La escala de calidad es el clavo!

Dejar respuesta

Please enter your comment!
Please enter your name here