From 36b4de2c7eb6f8bb35632afc53aed0e6b0b68af6 Mon Sep 17 00:00:00 2001 From: M66B Date: Tue, 29 Jan 2019 13:29:39 +0000 Subject: [PATCH] Improved image handling --- .../java/eu/faircode/email/AdapterImage.java | 31 +-- .../main/java/eu/faircode/email/Helper.java | 18 ++ .../java/eu/faircode/email/HtmlHelper.java | 218 +++++++++--------- 3 files changed, 134 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/AdapterImage.java b/app/src/main/java/eu/faircode/email/AdapterImage.java index 83ad11dadb..c4578d8899 100644 --- a/app/src/main/java/eu/faircode/email/AdapterImage.java +++ b/app/src/main/java/eu/faircode/email/AdapterImage.java @@ -24,9 +24,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; @@ -81,31 +78,13 @@ public class AdapterImage extends RecyclerView.Adapter private void bindTo(EntityAttachment attachment) { if (attachment.available) { - Drawable d = null; - File file = EntityAttachment.getFile(context, attachment.id); - - if ("image/jpeg".equals(attachment.type) || "image/png".equals(attachment.type)) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(), options); - - int scaleTo = context.getResources().getDisplayMetrics().widthPixels / 2; - int factor = Math.min(options.outWidth / scaleTo, options.outWidth / scaleTo); - if (factor > 1) { - options.inJustDecodeBounds = false; - options.inSampleSize = factor; - Bitmap scaled = BitmapFactory.decodeFile(file.getAbsolutePath(), options); - d = new BitmapDrawable(scaled); - } - } - - if (d == null) - d = BitmapDrawable.createFromPath(file.getAbsolutePath()); - - if (d == null) + Bitmap bm = Helper.decodeImage( + EntityAttachment.getFile(context, attachment.id), + context.getResources().getDisplayMetrics().widthPixels / 2); + if (bm == null) image.setImageResource(R.drawable.baseline_broken_image_24); else - image.setImageDrawable(d); + image.setImageBitmap(bm); } else image.setImageResource(attachment.progress == null ? R.drawable.baseline_image_24 : R.drawable.baseline_hourglass_empty_24); diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 7517160a47..b181aafa94 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -31,6 +31,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; @@ -218,6 +220,22 @@ public class Helper { return color; } + static Bitmap decodeImage(File file, int scaleToPixels) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + + int factor = Math.min(options.outWidth / scaleToPixels, options.outWidth / scaleToPixels); + if (factor > 1) { + Log.i("Decode image factor=" + factor); + options.inJustDecodeBounds = false; + options.inSampleSize = factor; + return BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } + + return BitmapFactory.decodeFile(file.getAbsolutePath()); + } + static void setViewsEnabled(ViewGroup view, boolean enabled) { for (int i = 0; i < view.getChildCount(); i++) { View child = view.getChildAt(i); diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index 6e38bbea31..01a8b1d3d4 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -36,10 +36,8 @@ import org.jsoup.safety.Whitelist; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -126,129 +124,135 @@ public class HtmlHelper { boolean embedded = source.startsWith("cid:"); boolean data = source.startsWith("data:"); - Log.i("Image embedded=" + embedded + " data=" + data + " source=" + source); - - if (show) { - // Embedded images - if (embedded) { - String cid = "<" + source.substring(4) + ">"; - EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(id, cid); - if (attachment == null) { - Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); - d.setBounds(0, 0, px, px); - return d; - } else if (!attachment.available) { - Drawable d = context.getResources().getDrawable(R.drawable.baseline_photo_library_24, context.getTheme()); - d.setBounds(0, 0, px, px); - return d; - } else { - File file = EntityAttachment.getFile(context, attachment.id); - Drawable d = Drawable.createFromPath(file.getAbsolutePath()); - if (d == null) { - d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); - d.setBounds(0, 0, px, px); - } else - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - return d; - } - } + Log.i("Image show=" + show + " embedded=" + embedded + " data=" + data + " source=" + source); - // Data URI - if (data) - try { - // "\"Red"; - - String base64 = source.substring(source.indexOf(',') + 1); - byte[] bytes = Base64.decode(base64.getBytes(), 0); - - Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - if (bm == null) - throw new IllegalArgumentException("decode byte array failed"); + if (!show) { + // Show placeholder icon + int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24); + Drawable d = context.getResources().getDrawable(resid, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } - Drawable d = new BitmapDrawable(context.getResources(), bm); - d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); - return d; - } catch (IllegalArgumentException ex) { - Log.w(ex); + // Embedded images + if (embedded) { + String cid = "<" + source.substring(4) + ">"; + EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(id, cid); + if (attachment == null) { + Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } else if (!attachment.available) { + Drawable d = context.getResources().getDrawable(R.drawable.baseline_photo_library_24, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } else { + Bitmap bm = Helper.decodeImage( + EntityAttachment.getFile(context, attachment.id), + context.getResources().getDisplayMetrics().widthPixels); + if (bm == null) { Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); d.setBounds(0, 0, px, px); return d; - } - - // Get cache folder - File dir = new File(context.getCacheDir(), "images"); - if (!dir.exists()) - dir.mkdir(); + } else + return new BitmapDrawable(bm); + } + } - InputStream is = null; - OutputStream os = null; + // Data URI + if (data) try { - // Create unique file name - File file = new File(dir, id + "_" + source.hashCode()); - - // Get input stream - if (file.exists()) { - Log.i("Using cached " + file); - is = new BufferedInputStream(new FileInputStream(file)); - } else { - Log.i("Downloading " + source); - try { - is = new URL(source).openStream(); - } catch (FileNotFoundException ex) { - throw ex; - } catch (IOException ex) { - Log.w(ex); - Drawable d = context.getResources().getDrawable(R.drawable.baseline_cloud_off_24, context.getTheme()); - d.setBounds(0, 0, px, px); - return d; - } - } + // "\"Red"; - // Decode image from stream - Bitmap bm = BitmapFactory.decodeStream(is); - if (bm == null) - throw new IllegalArgumentException("decode stream failed"); + String base64 = source.substring(source.indexOf(',') + 1); + byte[] bytes = Base64.decode(base64.getBytes(), 0); - // Cache bitmap - if (!file.exists()) { - os = new BufferedOutputStream(new FileOutputStream(file)); - bm.compress(Bitmap.CompressFormat.PNG, 100, os); - } + Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + if (bm == null) + throw new IllegalArgumentException("decode byte array failed"); - // Create drawable from bitmap Drawable d = new BitmapDrawable(context.getResources(), bm); d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); return d; - } catch (Throwable ex) { - // Show warning icon - Log.e(ex); + } catch (IllegalArgumentException ex) { + Log.w(ex); Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); d.setBounds(0, 0, px, px); return d; + } + + // Get cache file name + File dir = new File(context.getCacheDir(), "images"); + if (!dir.exists()) + dir.mkdir(); + File file = new File(dir, id + "_" + source.hashCode()); + + if (file.exists()) { + Log.i("Using cached " + file); + return Drawable.createFromPath(file.getAbsolutePath()); + } + + try { + InputStream probe = null; + BitmapFactory.Options options = new BitmapFactory.Options(); + try { + Log.i("Probe " + source); + probe = new URL(source).openStream(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(probe, null, options); } finally { - // Close streams - if (is != null) { - try { - is.close(); - } catch (IOException e) { - Log.w(e); - } - } - if (os != null) { - try { - os.close(); - } catch (IOException e) { - Log.w(e); - } - } + if (probe != null) + probe.close(); } - } else { - // Show placeholder icon - int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24); - Drawable d = context.getResources().getDrawable(resid, context.getTheme()); + + Bitmap bm; + InputStream is = null; + try { + Log.i("Download " + source); + is = new URL(source).openStream(); + + int scaleTo = context.getResources().getDisplayMetrics().widthPixels; + int factor = Math.min(options.outWidth / scaleTo, options.outWidth / scaleTo); + if (factor > 1) { + Log.i("Download image factor=" + factor); + options.inJustDecodeBounds = false; + options.inSampleSize = factor; + bm = BitmapFactory.decodeStream(is, null, options); + } else + bm = BitmapFactory.decodeStream(is); + } finally { + if (is != null) + is.close(); + } + + if (bm == null) + throw new FileNotFoundException("Download image failed"); + + Log.i("Downloaded image"); + + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + bm.compress(Bitmap.CompressFormat.PNG, 100, os); + } finally { + if (os != null) + os.close(); + } + + // Create drawable from bitmap + Drawable d = new BitmapDrawable(context.getResources(), bm); + d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); + return d; + } catch (Throwable ex) { + // Show warning icon + Log.w(ex); + int res = (ex instanceof IOException && !(ex instanceof FileNotFoundException) + ? R.drawable.baseline_cloud_off_24 + : R.drawable.baseline_broken_image_24); + Drawable d = context.getResources().getDrawable(res, context.getTheme()); d.setBounds(0, 0, px, px); return d; }