Preservar la calidad de la imagen cuando decodifique el flujo en android
Tengo una imagen en sdcard y necesito mostrarla en la vista de la imagen
El problema es que después de haber sido decodificado, parece que la calidad se deteriora. ¿Hay alguna manera de mantener la calidad y al mismo tiempo preservar la memoria?
- Android: lee la imagen PNG sin alpha y decodifica como ARGB_8888
- Conversión de objetos JSON con Bitmaps
- ¿Cómo saber cuánto tamaño de montón libre está disponible para bitmap para Android 2. *?
- Uso de Fresco de Facebook para cargar un mapa de bits
- Agregar un círculo de cuadro redondo en mapa de bits redondeado
¿O, si utilizé una imagen más grande, hay alguna manera de preservar la memoria (evitar el mapa de bits demasiado grande para cargar) con escala? (Necesito mantener el tamaño de la imagen original)
Gracias por ayudar.
public Bitmap decodeFile(String pubKey, int bookPageID, int type) throws IOException { Bitmap b = null; File f = null; String uri = null; FileInputStream fis = null; Log.d(TAG,"pageID to read: " + bookPageID); IRIssue issue = Broker.model.issueDataStore.getIRIssue(pubKey); String imageFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey); // pageID - 1 since the page is an array (start at 0) , but page ID start at 1 if (type == 2){ uri = imageFolder + issue.vol[0].pages[bookPageID - 1].graphicUri; }else { uri = imageFolder + issue.vol[0].pages[bookPageID - 1].textUri; } f = new File(uri); Log.d(TAG,"is file: " + uri + " exist?" + f.exists()); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; options.inJustDecodeBounds = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; fis = new FileInputStream(f); b = BitmapFactory.decodeStream(fis, null, options); fis.close(); return b; }
- Cuadros de bits en escala de grises en android
- Opengl es 2.0 dibujar superposición de mapa de bits en vídeo
- android convertir mapa de bits a bufferedinputstream
- Configuración de varios elementos personalizados en MultiAutoCompleteTextView: Android
- ¿Por qué setImageBitmap no tiene efecto en ImageButton personalizado?
- Mascarilla de mapa de bits eficiente con máscara alfa en blanco y negro en Android
- Cómo devolver el mapa de bits de la actividad secundaria en android
- ¿Qué hacen setDither, setFilterBitmap y setAntiAlias en una lona?
El siguiente código utiliza varios conceptos de Mostrar mapas de bits de manera eficiente
En primer lugar la lectura de mapa de bits se realiza en un hilo de fondo, estoy usando marca / reset en inputStream
(envuelto con BufferedInputstream
) para no leer más de lo necesario de la corriente cuando tratamos de averiguar el tamaño de la imagen para usar al calcular la escala factor. El código de ejemplo a continuación sub-muestra la imagen para que coincida con un tamaño de 320 x 240 píxeles. En un código de no ejemplo uno podría tener interfaz de devolución de llamada simple enviar el mapa de bits de onPostExecute
a implementar clase (implementador de interfaz de devolución de llamada). O proporcione la vista como un miembro al AsyncTask
directamente y establezca el mapa de bits en onPostExecute
.
Llame al código con (ejemplo de imagen descargada en mi dispositivo):
BitmapTask task = new BitmapTask(getContentResolver()); task.execute(Uri.parse("file:///storage/emulated/0/Download/download.jpg"));
Las clases en cuestión
private static class BitmapTask extends AsyncTask<Uri, Void, Bitmap> { // prevent mem leaks private WeakReference<ContentResolver> mWeakContentResolver; public BitmapTask(ContentResolver resolver) { mWeakContentResolver = new WeakReference<ContentResolver>(resolver); } @Override protected Bitmap doInBackground(Uri... params) { Bitmap bitmap = null; ContentResolver resolver = mWeakContentResolver.get(); if (resolver != null) { BufferedInputStream stream = null; try { stream = new BufferedInputStream( resolver.openInputStream(params[0])); stream.mark(1 * 1024); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // Find out size of image BitmapFactory.decodeStream(stream, null, options); try { stream.reset(); } catch (IOException e) { Log.d(TAG, "reset failed"); } int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; Log.d(TAG, "w, h, mime " + imageWidth + " , " + imageHeight + " , " + imageType); options.inJustDecodeBounds = false; // Calculate down scale factor options.inSampleSize = calculateInSampleSize(options, 320, 240); return BitmapFactory.decodeStream(stream, null, options); } catch (FileNotFoundException e) { bitmap = null; } finally { IOUtils.closeStreamSilently(stream); } } return bitmap; } @Override protected void onPostExecute(Bitmap result) { Log.d(TAG, "bitmap result: " + ((result != null) ? "" + result.getByteCount() : "0")); result.recycle(); } } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and // keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
Editar: Para los grandes inputstreams podría haber un problema con la marca / reset técnica, SkImageDecoder::Factory returned null
se puede ver en los registros a veces, resultando en mapa de bits nulo, se otra pregunta SO sobre el asunto: SkImageDecoder :: Factory devuelto null . Se puede arreglar reiniciando la variable de flujo de nuevo stream = new resolver.openInputStream(params[0]));
antes de regresar en doInBackground
Editar 2: Si tiene que conservar el tamaño de la imagen, pero no quiere limitar el uso de la memoria, puede usar las options.inPreferredConfig = Bitmap.Config.RGB_565;
que reduce a la mitad la memoria por píxel, pero tener en cuenta que las imágenes podrían no tener una gran calidad (experimento!).
Utilizo una clase BitmapHandler personalizada para resolver este problema:
public class BitmapHandler { private static int IMAGE_MAX_SIZE = 540; //This can be set to whatever you see fit private static String TAG = "BitmapHandler.java"; public BitmapHandler(Context ctx){ WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x; int height = size.y; Log.v(TAG, "Screen width: " + width + " height: " + height); IMAGE_MAX_SIZE = (Math.min(width, height))*4; //Try playing with this multiplier number to get different degrees of scaling } public Bitmap decodeFileAsPath(String uri) { // Create a file out of the uri File f = null; Log.v(TAG, "Incoming uri: " + uri); f = new File(uri); if (f.equals(null)){ Log.v(TAG, "File is null!"); } return decodeFile(f); } private Bitmap decodeFile(File f) { Bitmap b = null; try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; o.inScaled = false; FileInputStream fis = new FileInputStream(f); BitmapFactory.decodeStream(fis, null, o); fis.close(); int scale = 1; Log.v(TAG, "Decode Image height: " + o.outHeight + " and width: " + o.outWidth); if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) { scale = (int) Math.pow( 2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5))); } Log.v(TAG, "Final scale: " + scale); // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inScaled = false; o2.inSampleSize = scale; fis = new FileInputStream(f); b = BitmapFactory.decodeStream(fis, null, o2); fis.close(); } catch (IOException e) { Log.v(TAG, e.getMessage()); } return b; } }
Esto escala dinámicamente su imagen mientras intenta evitar una excepción OutOfMemory
BitmapFactory tiene una propiedad inSampleSize que fue diseñada para resolver este problema. Consulte los documentos: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize
Este artículo escribe acerca de cómo manejar mapas de bits eficientemente: http://developer.android.com/training/displaying-bitmaps/index.html
Mientras que la decodificación agrega options.injustdecodeBounds = true porque eso demuestra que usted quiere solamente los límites no el mapa de bits entero. Esto evitará los errores de memoria porque sólo cargará el tamaño de imagen que realmente necesita.
La segunda cosa es escalar ese mapa de bits según su necesidad y para hacer eso sin la distorsión usted tiene que escalarlo de una manera que mantenga la proporción de aspecto. Al hacer clic en la imagen, se establece una ración fija en la que se hace clic en la imagen y que la escala de esa imagen en esa proporción sólo. Si no está en su mano de lo que puede utilizar el siguiente método para obtener el tamaño insample y que decodificar la imagen a un tamaño particular sin distorsión.
private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) { float bitmapWidth = width; float bitmapHeight = height; int bitmapResolution = (int) (bitmapWidth * bitmapHeight); int targetResolution = targetWidth * targetHeight; int sampleSize = 1; if (targetResolution == 0) { return sampleSize; } for (int i = 1; (bitmapResolution / i) > targetResolution; i *= 2) { sampleSize = i; } return sampleSize;
}
Proporcione cualquier comentario si encuentra alguna mejora.
Un enfoque rápido que también es altamente configurable es utilizar un WebView
lugar de un ImageView
:
WebView mWebView = (WebView) findViewById(R.id.webview); mWebView.getSettings().setAllowFileAccess(true); mWebView.getSettings().setBuiltInZoomControls(true); String base = Environment.getExternalStorageDirectory().getAbsolutePath().toString(); String imagePath = "file://" + base + "/myImage.png";//replace with the name of the image you are accessing String html = "<html><head></head><body><img src=\"" + imagePath + "\"></body></html>"; mWebView.loadDataWithBaseURL("", html, "text/html","utf-8", "");
GridViewActivity.java
public class GridViewActivity extends Activity implements OnItemClickListener { private String[] filepathstring; private File[] listfile; GridView grid_sdcard; File file; ImageView image; GridViewAdapter adapter; int select; int sele; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.gridview_activity); image=(ImageView)convertView.findViewById(R.id.image_show); grid_sdcard=(GridView)findViewById(R.id.grid_sdcard); if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(this,"Error! No SDCARD Found!", Toast.LENGTH_LONG).show(); } else { file=new File(Environment.getExternalStorageDirectory() + File.separator +"eMENU Images/"); file.mkdirs(); Toast.makeText(GridViewActivity.this,"Past Image Here:", Toast.LENGTH_LONG).show(); } if(file.isDirectory()) { listfile=file.listFiles(); for(int i=0;i<listfile.length;i++) { filepathstring[i]=listfile[i].getAbsolutePath(); } } adapter=new GridViewAdapter(GridViewActivity.this,filepathstring); grid_sdcard.setAdapter(adapter); grid_sdcard.setOnItemClickListener(this); } @Override public void onItemClick(AdapterView<?> parent, View v, int position, long id1) { final String image=filepathstring[position]; Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]); imageshow.setImageBitmap(bitmap); } }
GridViewAdapter.java
public class GridViewAdapter extends BaseAdapter { String[] filepathlist; Context context; public GridViewAdapter(Context con, String[] filepathstring) { context=con; filepathlist=filepathstring; } @Override public int getCount() { return filepathlist.length; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return position; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null) { LayoutInflater inflater=(LayoutInflater)convertView.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView=inflater.inflate(R.layout.griadview_adapter,null); } ImageView imageshow=(ImageView)convertView.findViewById(R.id.image_show); Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]); imageshow.setImageBitmap(bitmap); return convertView; } }
- No se puede importar ActionBarCompat desde Android Support Library utilizando Android Studio
- Evite bloquear la pantalla mientras ProgressDialog está mostrando