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:
.asBitmap().toBytes(...) so you have the full benefit of GlideFile; 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:
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
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!
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:
Tip:
Environment.getExternalStorageDirectory() + "/Android/data/" + context.getApplicationContext().getPackageName()should be the same asgetContext().getExternalFilesDir(null), except the latter is more portable.Update: @sjudd translated this to v4: https://github.com/bumptech/glide/issues/3399#issuecomment-443796562