Glide: From glide file to direct share without creating more image on smartphone...

Created on 23 Aug 2016  路  11Comments  路  Source: bumptech/glide

Original comments from #459:


@juani21 said:

hi, i have a class that compress image but it need of file path, i take the uri with the Uri with ShareTask class, but not work , there is a problem with filepath that is without extension (i think), i resolve that so:

-get bitmap from glide
-create new file from bitmap
-take uri from file
-pass the uri to my class for compression

this work but i have more image , 1 for create new file from bitmap, 1 compressed image , 1 shared image.

i will want that the uri from ShareTask is good for passing to my compression class...

thank you, i hope in your help


@TWiStErRob said:

  1. The ShareTask above works with a File object, yes it doesn't have an extension, but it's easier to make a temporary copy (e.g. view Streams), than decode to Bitmap and recompress just to give it an extension.
  2. All that highly depends on what your "compression class" is and does
    2a. For example if all it does is compress as PNG or JPEG with specific quality then use .asBitmap().toBytes(...) so you have the full benefit of Glide
    2b. Otherwise you should not make it dependent on having a File; cr.openInputStream(Uri) should be the way (see https://github.com/bumptech/glide/issues/459#issuecomment-199783078), and file type can be determined either by magic bytes or cr.getType() (https://github.com/bumptech/glide/issues/459#issuecomment-100238254 is the "server" side of getType())

Glide Version: 3.7.0

Integration libraries: okhhtp3

Device/Android Version: nexus 5 Marshmallow

Issue details / Repro steps / Use case background:

hi, i have a class that compress image but it need of file path, i take the uri with the Uri with ShareTask class, but not work , there is a problem with filepath that is without extension (i think), i resolve that so:

-get bitmap from glide

Glide.with(this)
    .load(link)
    .asBitmap().error(R.drawable.ic_error)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(new BitmapImageViewTarget(placeImage) {
        @Override
        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
            super.onResourceReady(bitmap, anim);
            shareBitmap = bitmap;
        }
    });

-create new file from bitmap e take uri

public static Uri getLocalBitmapUri(Bitmap bmp) {
    // Store image to default external storage directory
    Uri bmpUri = null;
    try {
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_DOWNLOADS), "share_image_" + System.currentTimeMillis() + ".png");
        file.getParentFile().mkdirs();
        FileOutputStream out = new FileOutputStream(file);
        bmp.compress(Bitmap.CompressFormat.PNG, 90, out);
        out.close();
        bmpUri = Uri.fromFile(file);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bmpUri;
}

-pass the uri to my class for compression

ImageCompression imageCompression1 = new ImageCompression(mContext) {
    @Override
    protected void onPostExecute(String imagePath) {
        super.onPostExecute(imagePath);
        Utils.shareImage(mContext, Uri.parse(imagePath));
    }
};
imageCompression1.execute(Utils.getLocalBitmapUri(shareBitmap).getPath());

this work but i have more image , 1 for create new file from bitmap, 1 compressed image , 1 shared image.

i will want that the uri from ShareTask is good for passing to my compression class...

this not work:

  • take file and uri from cache
public class ShareTask2 extends AsyncTask<String, Void, File> {

    private final Context context;

    public ShareTask2(Context context) {
        this.context = context;
    }

    @Override
    protected File doInBackground(String... params) {
        String url = params[0]; // should be easy to extend to share multiple images at once
        Bitmap bmp = null;
        Uri bmpUri;
        try {
            File file = Glide
                    .with(context)
                    .load(url)
                    .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
                    .get() // needs to be called on background thread
                    ;
            return file;
        } catch (Exception ex) {
            Log.w("SHARE", "Sharing " + " failed", ex);
            return null;
        }
    }

    @Override
    protected void onPostExecute(File result) {
        if (result == null) {
            return;
        }

        Uri uri = FileProvider.getUriForFile(context, "com.xxx.fileprovider", result);

        ImageCompression imageCompression1 = new ImageCompression(context) {
            @Override
            protected void onPostExecute(String imagePath) {
                super.onPostExecute(imagePath);
                Log.i("imagecompression","onpostexcute");
                share(Uri.parse(imagePath));
            }
        };
        imageCompression1.execute(uri.getPath());
        //share(uri); // startActivity probably needs UI thread
    }

    private void share(Uri result) {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("image/jpeg");
        intent.putExtra(Intent.EXTRA_SUBJECT, "Shared image");
        intent.putExtra(Intent.EXTRA_TEXT, "Look what I found!");
        intent.putExtra(Intent.EXTRA_STREAM, result);
        context.startActivity(Intent.createChooser(intent, "Share image"));
    }
}

Stack trace / LogCat:

E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /share/dbdf49a9900debd1cdae3c3be088df142ac4e2054a462a6f91a4475fc9660895.0: open failed: ENOENT (No such file or directory)
08-23 17:12:06.386 17670-18153/com.xxx.xxx E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2
      Process: com.xxx.xxx, PID: 17670
      java.lang.RuntimeException: An error occurred while executing doInBackground()
          at android.os.AsyncTask$3.done(AsyncTask.java:309)
          at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
          at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
          at java.util.concurrent.FutureTask.run(FutureTask.java:242)
          at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
          at java.lang.Thread.run(Thread.java:818)
       Caused by: java.lang.IllegalArgumentException: width and height must be > 0
          at android.graphics.Bitmap.createBitmap(Bitmap.java:829)
          at android.graphics.Bitmap.createBitmap(Bitmap.java:808)
          at android.graphics.Bitmap.createBitmap(Bitmap.java:775)

I try to edit the FilePovider inline with https://github.com/bumptech/glide/issues/459#issuecomment-100238254 but not work or i wrong something.

maybe i don't understand this solution https://github.com/bumptech/glide/issues/459#issuecomment-241574350 ...sorry...

thank you for all

question

Most helpful comment

I think you're trying to do this, though I might be wrong. Give it a spin and let me know if it doesn't satisfy your requirements:

Glide // execute this on UI thread!
    .with(this)
    .load(link)
    .asBitmap()
    .toBytes(CompressFormat.JPEG, 80)
    .format(DecodeFormat.PREFER_ARGB_8888)
    .atMost()
    .override(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) // 1280 I guess
    .diskCacheStrategy(DiskCacheStrategy.SOURCE) // read it from cache
    .skipMemoryCache(true) // don't save in memory, needed only once
    .into(new SimpleTarget<byte[]>() {
        @Override public void onResourceReady(byte[] resource, GlideAnimation<? super byte[]> ignore) {
            new SaveAsFileTask().execute(resource);
        }
        @Override public void onLoadFailed(Exception ex, Drawable ignore) {
            toastUser("Whops, can't load " + link);
        }
    })
;
class SaveAsFileTask extends AsyncTask<byte[], Void, File> {
    @Override protected File doInBackground(byte[]... params) {
        try {
            File target = new File(ImageCompression.getFilename());
            OutputStream out = new FileOutputStream(target);
            out.write((byte[])params[0]);
            return target;
        } catch(IOException ex) {
            return null;
        }
    }
    @Override protected void onPostExecute(@Nullable File result) {
        Uri uri = FileProvider.getUriForFile(context, "com.xxx.fileprovider", result);
        share(uri);
    }
}

Tip: Environment.getExternalStorageDirectory() + "/Android/data/" + context.getApplicationContext().getPackageName() should be the same as getContext().getExternalFilesDir(null), except the latter is more portable.

Update: @sjudd translated this to v4: https://github.com/bumptech/glide/issues/3399#issuecomment-443796562

All 11 comments

What does ImageCompression do?

public class ImageCompression extends AsyncTask<String, Void, String> {

    private Context context;
    private static final float maxHeight = 1280.0f;
    private static final float maxWidth = 1280.0f;


    public ImageCompression(Context context) {
        this.context = context;
    }

    @Override
    protected String doInBackground(String... strings) {
        if (strings.length == 0 || strings[0] == null)
            return null;

        return compressImage(strings[0]);
    }

    protected void onPostExecute(String imagePath) {
        // imagePath is path of new compressed image.
    }


    public String compressImage(String imagePath) {
        Bitmap scaledBitmap = null;

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bmp = BitmapFactory.decodeFile(imagePath, options);

        int actualHeight = options.outHeight;
        int actualWidth = options.outWidth;

        float imgRatio = (float) actualWidth / (float) actualHeight;
        float maxRatio = maxWidth / maxHeight;

        if (actualHeight > maxHeight || actualWidth > maxWidth) {
            if (imgRatio < maxRatio) {
                imgRatio = maxHeight / actualHeight;
                actualWidth = (int) (imgRatio * actualWidth);
                actualHeight = (int) maxHeight;
            } else if (imgRatio > maxRatio) {
                imgRatio = maxWidth / actualWidth;
                actualHeight = (int) (imgRatio * actualHeight);
                actualWidth = (int) maxWidth;
            } else {
                actualHeight = (int) maxHeight;
                actualWidth = (int) maxWidth;

            }
        }

        options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inTempStorage = new byte[16 * 1024];

        try {
            bmp = BitmapFactory.decodeFile(imagePath, options);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();

        }
        try {
            scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.RGB_565);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();
        }

        float ratioX = actualWidth / (float) options.outWidth;
        float ratioY = actualHeight / (float) options.outHeight;
        float middleX = actualWidth / 2.0f;
        float middleY = actualHeight / 2.0f;

        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);

        Canvas canvas = new Canvas(scaledBitmap);
        canvas.setMatrix(scaleMatrix);
        canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));

        if (bmp != null) {
            bmp.recycle();
        }

        ExifInterface exif;
        try {
            exif = new ExifInterface(imagePath);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
            Matrix matrix = new Matrix();
            if (orientation == 6) {
                matrix.postRotate(90);
            } else if (orientation == 3) {
                matrix.postRotate(180);
            } else if (orientation == 8) {
                matrix.postRotate(270);
            }
            scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        FileOutputStream out = null;
        String filepath = getFilename();
        try {
            out = new FileOutputStream(filepath);

            //write the compressed bitmap at the destination specified by filename.
            scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return filepath;
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        final float totalPixels = width * height;
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;

        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++;
        }

        return inSampleSize;
    }

    public String getFilename() {
        File mediaStorageDir = new File(Environment.getExternalStorageDirectory()
                + "/Android/data/"
                + context.getApplicationContext().getPackageName()
                + "/Files/Compressed");

        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            mediaStorageDir.mkdirs();
        }

        String mImageName = "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg";
        String uriString = (mediaStorageDir.getAbsolutePath() + "/" + mImageName);
        ;
        return uriString;

    }
}

I think you're trying to do this, though I might be wrong. Give it a spin and let me know if it doesn't satisfy your requirements:

Glide // execute this on UI thread!
    .with(this)
    .load(link)
    .asBitmap()
    .toBytes(CompressFormat.JPEG, 80)
    .format(DecodeFormat.PREFER_ARGB_8888)
    .atMost()
    .override(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) // 1280 I guess
    .diskCacheStrategy(DiskCacheStrategy.SOURCE) // read it from cache
    .skipMemoryCache(true) // don't save in memory, needed only once
    .into(new SimpleTarget<byte[]>() {
        @Override public void onResourceReady(byte[] resource, GlideAnimation<? super byte[]> ignore) {
            new SaveAsFileTask().execute(resource);
        }
        @Override public void onLoadFailed(Exception ex, Drawable ignore) {
            toastUser("Whops, can't load " + link);
        }
    })
;
class SaveAsFileTask extends AsyncTask<byte[], Void, File> {
    @Override protected File doInBackground(byte[]... params) {
        try {
            File target = new File(ImageCompression.getFilename());
            OutputStream out = new FileOutputStream(target);
            out.write((byte[])params[0]);
            return target;
        } catch(IOException ex) {
            return null;
        }
    }
    @Override protected void onPostExecute(@Nullable File result) {
        Uri uri = FileProvider.getUriForFile(context, "com.xxx.fileprovider", result);
        share(uri);
    }
}

Tip: Environment.getExternalStorageDirectory() + "/Android/data/" + context.getApplicationContext().getPackageName() should be the same as getContext().getExternalFilesDir(null), except the latter is more portable.

Update: @sjudd translated this to v4: https://github.com/bumptech/glide/issues/3399#issuecomment-443796562

This part of code is better in background ?

Glide.with(this) .load(link) .asBitmap() .toBytes(CompressFormat.JPEG, 80) .format(DecodeFormat.PREFER_ARGB_8888) ....

and .override(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) change the aspect ratio of image?
the constant MAX_IMAGE_SIZE is my choise?

thank you very much i'm a beginner

This part of code is better in background ?

Glide delegates all the loads into the background automatically, so all Glide loads are designed to be called on the UI thread. Glide will execute the decoding on a pooled thread, and then give you the response (via listener or into target) on the UI thread again. This is why you need the SaveAsFileTask above.

change the aspect ratio of image?

No, it doesn't. .override() defines how big the target size is. How the image is resized/scaled/cropped into that rectangle is a separate story. The above is only using the atMost downsampler, which means that it'll only use inSampleSize and therefore won't distort the image. Your other option would be to use .fitCenter() instead of .atMost(), which behaves more like an ImageView load, but it would be slower in this case.

the constant MAX_IMAGE_SIZE is my choise?

Yes, it's not a built-in constant. It's the same as ImageCompression.maxWidth/maxHeight.

ok thank you very muck , i undestrand

another question....

i see that the ImageCompression class is not used, you use only this method ImageCompression.getFilename(), I wrong?

if it is so i not need of ImageCompression class, right? or i can use this in onPostExecute for compress more the image?

thank you

Yes, you can move that single method where you need it. Those few lines of Glide do pretty much the same as that whole class.

ok thank you..... i have an error

Stack trace / LogCat:

FATAL EXCEPTION: main
                                                                      Process: com.xxx.xxx, PID: 28748
                                                                      java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.xxx.xxx/Files/Compressed/IMG_1471976580964.jpg
                                                                          at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:711)
                                                                          at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
                                                                          at com.xxx.xxx.SaveAsFileTask.onPostExecute(SaveAsFileTask.java:41)
                                                                          at com.xxx.xxx.SaveAsFileTask.onPostExecute(SaveAsFileTask.java:19)

this is my files_path.xml

<?xml version="1.0" encoding="UTF-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <files-path
        name="my_images"
        path="Images/" />

    <!-- Based on default Glide operation, see InternalCacheDiskCacheFactory instantiated from GlideBuilder.createGlide() -->
    <cache-path
        name="share"
        path="image_manager_disk_cache"
        tools:path="DiskCache.Factory.DEFAULT_DISK_CACHE_DIR" />
</paths>

NB: i don t undesrtand because the insert code isn t formatted...sorry

You need to use getExternalFilesDir(null) with <external-files-path. Read the documentation at https://developer.android.com/reference/android/support/v4/content/FileProvider.html

Thank you very much is perfect!! you are great....

This is absolutely awesome!

Thank you @TWiStErRob && the rest of the team!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

billy2271 picture billy2271  路  3Comments

kenneth2008 picture kenneth2008  路  3Comments

kooeasy picture kooeasy  路  3Comments

Anton111111 picture Anton111111  路  3Comments

Ncit picture Ncit  路  3Comments