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
- plataforma transformada independant lib donde todas las referencias awt son reemplazadas por la interfaz de
- pixymeta-j2se-lib
- Implementación awt / j2se de
IBitmap
- Implementación awt / j2se de
- pixymeta-android-lib
- implementación android de
IBitmap
- implementación android de
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.