android bitmap image-resizing bicubic lanczos

Android: redimensionamiento de mapa de bits con mejor algoritmo de remuestreo que bilineal(como Lanczos3)



bitmap image-resizing (5)

Hace poco escribí esto para escalar / recortar una imagen a una resolución específica y comprimirla con calidad:

public static void scaleImageToResolution(Context context, File image, int dstWidth, int dstHeight) { if (dstHeight > 0 && dstWidth > 0 && image != null) { Bitmap result = null; try { //Get Image Properties BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions); int photoH = bmOptions.outHeight; int photoW = bmOptions.outWidth; bmOptions.inJustDecodeBounds = false; bmOptions.inPurgeable = true; //Smaller Image Size in Memory with Config bmOptions.inPreferredConfig = Bitmap.Config.RGB_565; //Is resolution not the same like 16:9 == 4:3 then crop otherwise fit ScalingLogic scalingLogic = getScalingLogic(photoW, photoH,dstWidth, dstHeight); //Get Maximum automatic downscaling that it''s still bigger then this requested resolution bmOptions.inSampleSize = calculateScalingSampleSize(photoW, photoH, dstWidth, dstHeight, scalingLogic); //Get unscaled Bitmap result = BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions); //Scale Bitmap to requested Resolution result = scaleImageToResolution(context, result, scalingLogic); if (result != null) { //Save Bitmap with quality saveImageWithQuality(context, result, image); } } finally { //Clear Memory if (result != null) result.recycle(); } } } public static void saveImageWithQuality(Bitmap bitmap, String path, int compressQuality) { try { FileOutputStream fOut; fOut = new FileOutputStream(path); bitmap.compress(Bitmap.CompressFormat.JPEG, compressQuality, fOut); fOut.flush(); fOut.close(); } catch (IOException ex) { if (Logger.getRootLogger() != null) Logger.getRootLogger().error(ex); else Log.e("saveImageWithQuality", "Error while saving compressed Picture: " + ex.getMessage() + StringUtils.newLine() + ex.getStackTrace().toString()); } } public static void saveImageWithQuality(Context context, Bitmap bitmap, File file) { saveImageWithQuality(bitmap, file.getAbsolutePath(), getCompressQuality()); } public static void saveImageWithQuality(Context context, Bitmap bitmap, String path) { saveImageWithQuality(bitmap, path, getCompressQuality()); } private static int calculateScalingSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.FIT) { final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect > dstAspect) { return srcWidth / dstWidth; } else { return srcHeight / dstHeight; } } else { final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect > dstAspect) { return srcHeight / dstHeight; } else { return srcWidth / dstWidth; } } } private static Bitmap scaleImageToResolution(Context context, Bitmap unscaledBitmap, ScalingLogic scalingLogic, int dstWidth, int dstHeight) { //Do Rectangle of original picture when crop Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); //Do Rectangle to fit in the source rectangle Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); //insert source rectangle into new one Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(scaledBitmap); canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG)); //Recycle the unscaled Bitmap afterwards unscaledBitmap.recycle(); return scaledBitmap; } private static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.CROP) { if (srcWidth >= srcHeight) { //Horizontal final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { final int srcRectHeight = (int) (srcWidth / dstAspect); final int scrRectTop = (srcHeight - srcRectHeight) / 2; return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); } else { final int srcRectWidth = (int) (srcHeight * dstAspect); final int srcRectLeft = (srcWidth - srcRectWidth) / 2; return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); } } else { //Vertikal final float srcAspect = (float) srcHeight / (float) srcWidth; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { final int srcRectWidth = (int) (srcHeight / dstAspect); final int srcRectLeft = (srcWidth - srcRectWidth) / 2; return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); } else { final int srcRectHeight = (int) (srcWidth * dstAspect); final int scrRectTop = (srcHeight - srcRectHeight) / 2; return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); } } } else { return new Rect(0, 0, srcWidth, srcHeight); } } private static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.FIT) { if (srcWidth > srcHeight) { //Vertikal final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { return new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight); } else { return new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect)); } } else { //Horizontal final float srcAspect = (float) srcHeight / (float) srcWidth; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { return new Rect(0, 0, (int) (dstHeight / srcAspect), dstHeight); } else { return new Rect(0, 0, dstWidth, (int) (dstWidth * srcAspect)); } } } else { if (srcWidth >= srcHeight) return new Rect(0, 0, dstWidth, dstHeight); else return new Rect(0, 0, dstHeight, dstWidth); } } private static ScalingLogic getScalingLogic(int imageWidth, int imageHeight, int dstResolutionWidth, int dstResolutionHeight) { if (imageWidth >= imageHeight) { //Bild horizontal final float srcAspect = (float) imageWidth / (float) imageHeight; final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight; if (!isResolutionEqual(srcAspect, dstAspect)) { return ScalingLogic.CROP; } else { return ScalingLogic.FIT; } } else { //Bild vertikal final float srcAspect = (float) imageHeight / (float) imageWidth; final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight; if (!isResolutionEqual(srcAspect, dstAspect)) { return ScalingLogic.CROP; } else { return ScalingLogic.FIT; } } } public enum PictureQuality { High, Medium, Low } public enum ScalingLogic { CROP, FIT } //Does resolution match private static boolean isResolutionEqual(float v1, float v2) { // Falls a 1.999999999999 and b = 2.000000000000 return v1 == v2 || Math.abs(v1 - v2) / Math.max(Math.abs(v1), Math.abs(v2)) < 0.01; } public int getCompressQuality() { if (Quality == PictureQuality.High) return 100; else if (Quality == PictureQuality.Medium) return 50; else if (Quality == PictureQuality.Low) return 25; else return 0; }

no está utilizando las bibliotecas que mencionaste, pero funciona y estoy contento con eso. Quizás tú también lo estés.

¿Hay alguna forma o biblioteca externa que pueda cambiar el tamaño de la imagen usando Lanczos (idealmente) o al menos bicúbico alg. en Android ? (Más rápido es mejor, por supuesto, pero la calidad es la prioridad, un tiempo de procesamiento es secundario)

Todo lo que tengo hasta ahora es esto:

Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

Sin embargo, utiliza un filtro bilineal y la calidad de salida es terrible. Especialmente si desea conservar detalles (como líneas finas o textos legibles).

Hay muchas buenas bibliotecas para Java, como se discute por ejemplo aquí: Java: cambiar el tamaño de la imagen sin perder calidad

Sin embargo, siempre depende de las clases Java awt como java.awt.image.BufferedImage , por lo que no se puede usar en Android.

¿Hay alguna manera de cómo cambiar el filtro predeterminado (bilineal) en el método Bitmap.createScaledBitmap() o alguna biblioteca como la lib de Morten Nobel que puede trabajar con la clase android.graphics.Bitmap (o con alguna representación cruda, como @Tron en el comentario ha señalado)?


Lamentablemente, Android usa android.graphics.Bitmap que no existe en java, mientras que java utiliza java.awt.image.BufferedImage que no existe en android :-(

No tengo a ready to use library for android pero una ruta para portar una lib específica de java-awt a una plataforma, java lib independiente con plataforma de manejadores específicos para android y awt / j2se

En la biblioteca de res escala java, debe ocultar todas las clases específicas de java-awt (como BufferedImage) detrás de una interfaz IBitmap e implementar esa interfaz para j2se e independientemente para Android.

Lo he hecho con éxito para el procesamiento de metadatos exif / icc / ipc y he implementado la interfaz pixymeta-lib /.../ IBitmap.java con la implementación de j2se pixymeta-j2se-lib /.../ j2se / BitmapNative.java y android pixymeta- android-lib /.../ android / BitmapNative.java

Entonces tengo estos paquetes

  • pixymeta-lib
    • plataforma transformada independant lib donde todas las referencias awt son reemplazadas por la interfaz de IBitmap
  • pixymeta-j2se-lib
    • Implementación awt / j2se de IBitmap
  • pixymeta-android-lib
    • implementación android de IBitmap

Aquí hay un código que he usado para cambiar el tamaño de la imagen ...

Bitmap photo1 ; private byte[] imageByteArray1 ; BitmapFactory.Options opt1 = new BitmapFactory.Options(); opt1.inJustDecodeBounds=true; BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),opt1); // The new size we want to scale to final int REQUIRED_SIZE=320; // Find the correct scale value. It should be the power of 2. int width_tmp=opt1.outWidth,height_tmp=opt1.outHeight; int scale=2; while(true){ if(width_tmp>REQUIRED_SIZE||height_tmp>REQUIRED_SIZE) break; width_tmp/=2; height_tmp/=2; scale*=2; } // Decode with inSampleSize BitmapFactory.Options o2=new BitmapFactory.Options(); o2.inSampleSize=scale; o2.inJustDecodeBounds=false; photo1=BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),o2); ByteArrayOutputStream baos1=new ByteArrayOutputStream(); photo1.compress(Bitmap.CompressFormat.JPEG,60,baos1); imageByteArray1=baos1.toByteArray();

Espero que te ayude..


La OMI más prometedora es usar libswscale (de FFmpeg), ofrece Lanczos y muchos otros filtros. Para acceder al búfer de Bitmap desde el código nativo puede usar jnigraphics . Este enfoque garantiza un buen rendimiento y resultados confiables.

EDITAR

Aquí puede encontrar la aplicación de demostración aproximada, que utiliza el enfoque propuesto. Por el momento, el rendimiento es frustrantemente malo, por lo que debe investigarse para decidir si debemos hacer algo para mejorarlo.


Si solo desea volver a muestrear la imagen de una manera que esté optimizada para fines de visualización, puede utilizar este ingenioso pequeño trazador de líneas que me ha servido bien.

Bitmap bitmap = new BitmapDrawable(getResources(), yourBitmap).getBitmap();

Esta línea de código puede parecer extraña porque está convirtiendo un mapa de bits en un mapa de bits extraíble y de nuevo en un mapa de bits, pero un mapa de bits se establece de manera predeterminada en la densidad de píxeles del dispositivo (a menos que use un constructor diferente).

Si necesita cambiar el tamaño también, solo sepárelo en dos líneas y use setBounds antes de convertir BitmapDrawable de nuevo a un mapa de bits de esta forma:

BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), yourBitmap); bitmapDrawable.setBounds(left, top, right, bottom); //Make it a new size in pixels. yourBitmap = bitmapDrawable.getBitmap(); //Convert it back to a bitmap optimised for display purposes.

El mapa de bits dibujable puede aparecer como privado pero no lo es, solo ciertos constructores están privados y el constructor en este ejemplo no está privado. También esto funcionará con API 4

Alternativamente, los documentos de Android tienen una muestra descargable para esto aquí: https://developer.android.com/topic/performance/graphics/load-bitmap.html

Espero que esto ayude.