Scale image

309 views
Skip to first unread message

Andre Classen

unread,
Apr 29, 2015, 7:34:29 AM4/29/15
to glidel...@googlegroups.com
I have a ListView with ImageViews.
Assume the ImageView has a size of 64dp x 64dp.
Loading an image into the imageviews is no problem.
But what i'm trying to archive is to scale the image down to e.g 32dp x 32dp.
I wan't to avoid using override() so my thought was i can do this with a custom transform function,
But this seems not to work.
Is there a way to archive this ?

Best regards

Róbert Papp

unread,
Apr 29, 2015, 7:48:30 AM4/29/15
to Andre Classen, glidel...@googlegroups.com
What's wrong with override()? I think that's the method for this.
Also what's your transformation code that doesn't work?

+Robi

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/glidelibrary/c9357edf-2e57-4803-ae32-36e330d6ec50%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Andre Classen

unread,
Apr 29, 2015, 7:54:43 AM4/29/15
to glidel...@googlegroups.com, acla...@hotmail.de
The problem using override is that i have the size in code.
But i want to define the size of my imageview in xml and my image should scaled down to imageview.width / 2.

This is my testing transformation code:

          final Bitmap toReuse = pool.get(2, 2, toTransform.getConfig() != null
                    ? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
            Bitmap transformed = TransformationUtils.fitCenter(toTransform, pool, 2, 2);
            if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
                toReuse.recycle();

Róbert Papp

unread,
Apr 29, 2015, 8:00:29 AM4/29/15
to Andre Classen, glidel...@googlegroups.com
How about the following "simple" Android way of solving this is:

res/values/dimens.xml:
<resources>
<dimen name="image_size">64dp</dimen>
</resources>

res/layout/item.xml:
<ImageView android:layout_width="@dimen/image_size" android:layout_height="@dimen/image_size" ... />

src/main/java/.../Adapter.java:
int size = context.getResources().getDimensionPixelSize(R.dimen.image_size) / 2; // 32 dp
.override(size, size)

You can even prefetch this in constructor so it's cached.
 
+Robi

François Blavoet

unread,
Apr 29, 2015, 8:01:44 AM4/29/15
to glidel...@googlegroups.com
couldn't you just use .sizeMultiplier(0.5f) here ?

Róbert Papp

unread,
Apr 29, 2015, 8:05:07 AM4/29/15
to François Blavoet, glidel...@googlegroups.com

+Robi

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
Apr 29, 2015, 8:12:52 AM4/29/15
to glidel...@googlegroups.com, francois...@gmail.com
Don't know why, but it dosen't work on all images.

http://snag.gy/ojKxf.jpg

Using it this way:

Glide.with(mContext)
.load(item.ImageRequest)
.sizeMultiplier(0.5f)
.fitCenter()
.into(holder.image);

Róbert Papp

unread,
Apr 29, 2015, 8:14:33 AM4/29/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet
What's your imageview.scaleType? (fitCenter() only affects transformation, how it is displayed afterwards is the matter of the target, in this case ImageView)

+Robi

Andre Classen

unread,
Apr 29, 2015, 8:16:25 AM4/29/15
to glidel...@googlegroups.com, francois...@gmail.com, acla...@hotmail.de

twist...@gmail.com

unread,
Apr 29, 2015, 8:22:57 AM4/29/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com
Are you trying to add padding around the images, because then you should use layout_width="32dp" android:layout_margin="16dp" so it adds up to 16+32+16=64.
If you don't want padding then change to fitCenter or centerCrop.
I agree, RTL looks weird though regardless of your intentions, it just doesn't fit in with the rest. Maybe try clearing the cache.
Message has been deleted

Andre Classen

unread,
Apr 29, 2015, 8:33:28 AM4/29/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com, twist...@gmail.com
To clearify what i want to archive here is the following :

In case the primary image , which should be displayed with 'centerCrop' , is not available (404 etc) i want to download a failover image.
The failover image should be resized to the half of the imageview size.

The complete xml for the listview item looks like this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:tools="http://schemas.android.com/tools"
   
android:layout_width="match_parent"
   
android:background="@color/white"
   
android:layout_height="@dimen/listItemHeightTwoLines">

   
<ImageView
       
android:id="@+id/image"
       
android:layout_width="@dimen/listItemHeightTwoLines"
       
android:layout_height="match_parent"
       
android:scaleType="center" />

   
<de.stanwood.onair.phonegap.controls.SimpleProgress
       
android:id="@+id/progress"
       
android:layout_toRightOf="@id/image"
       
android:layout_width="1dp"
       
android:layout_height="wrap_content"
       
android:background="@color/lightRed"
       
android:layout_marginLeft="1dp" />

   
<TextView
       
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
       
android:id="@+id/title"
       
style="@style/Title"
       
android:layout_toRightOf="@id/progress"
       
android:paddingLeft="@dimen/padding"
       
android:paddingRight="@dimen/padding"
       
android:paddingTop="@dimen/padding" />

   
<TextView
       
android:id="@+id/subhead"
       
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
       
style="@style/Subhead"
       
android:layout_below="@id/title"
       
android:layout_toRightOf="@id/progress"
       
android:paddingLeft="@dimen/padding"
       
android:paddingRight="@dimen/padding" />
</RelativeLayout>


Sam Judd

unread,
Apr 29, 2015, 10:07:46 AM4/29/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet, twist...@gmail.com
Just to make sure I'm understanding you:

You want the full image to fill the view using center crop. If that fails, you want a half size images, in the original aspect ratio, centered inside the view?

If so, do you get incorrectly sized images with sizeMultiplier? Can you try logging the size that's loaded for the RTL image for example?

Glide should definitely be providing you the size image you ask for with the transformation you ask for. It's totally possible there's a bug there that we need to fix. However, it's equally possible it's just an issue with the layout itself. I don't see anything obviously wrong with the xml you provided, but that can be hard to eyeball. The only thought I have is that you could replace match_parent in your ImageView with just @dimen/listItemHeightTwoLines, since that's always your parent height.

Sam

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
Apr 29, 2015, 5:51:28 PM4/29/15
to glidel...@googlegroups.com, twist...@gmail.com, francois...@gmail.com, acla...@hotmail.de
Yes, this is what i finaly want to archive.
But i can reproduce the same problem with the flicker sample.
I've only modified the sample the following way:

FlickrPhotoGrid.java :

fullRequest
        .load(current)
       
.sizeMultiplier(0.1f)
               
.centerCrop()
        .into(imageView);

flickr_photo_grid_item.xml:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:scaleType="center"
    android:layout_width="64dp"
    android:layout_height="64dp"
    android:contentDescription="@string/image_description" />

Search for 'test':

http://snag.gy/BVW1Y.jpg

After scrolling down:

http://snag.gy/1FHss.jpg

Is this a known 'bug' or where is my bug ?
I think i'm not doing something 'magical' here ?! ;)

Andre

Róbert Papp

unread,
Apr 29, 2015, 6:04:00 PM4/29/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet
Can you please check how replacing crossFade with dontAnimate affect this? Do it in both fullRequest and thumbRequest.
This might be a manifestation of https://github.com/bumptech/glide/issues/363

+Robi

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
Apr 29, 2015, 6:11:25 PM4/29/15
to glidel...@googlegroups.com, francois...@gmail.com, acla...@hotmail.de
Tested with dontAnimate() but the problem is still present.

Róbert Papp

unread,
Apr 29, 2015, 8:01:35 PM4/29/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet
Setup:
I found something. I did what you did plus disabled preloading and stuff because it's really hard to debug that way. Also modified the grid to have 1 column for less logging/breakpoint hits.

For some reason at every scroll (when a new image comes into view) the list binds position=0 too, let's ignore this for now, it's probably a quirk of the flickr sample itself.

Repro and debug:
They loaded stretched already after searching, so I was only able to see the small images if I went to LIST tab and then back. And after that they were stretched when started scrolling. For the first batch (switching tabs) the views are not laid out yet so they are 0x0, but their layout knows the size, so SizeDeterminer responds immediately. When the resource is set to GlideDrawableImageViewTarget viewRatio = NaN (0/0) so no squaring this time. After this batch has been loaded I started scrolling and the view sizes are now correct and of course viewRatio=1 (because you set it to 64dp*64dp). Now both the drawable and the view are squared and hence SquaringDrawable is used, the problem is that it's using the view's width as intrinsic size.

Validation: replacing
new SquaringDrawable(resource, view.getWidth());
with
new SquaringDrawable(resource, resource.getIntrinsicWidth());
solves this issue and images are now displayed as in the first image, all the time. Sadly the comment above it explains thoroughly that it was intented to be view.getWidth().


Solution: So rather than changing that you can work around SquaringDrawable like this:
@SuppressWarnings("unchecked") ImageViewTarget<GlideDrawable> target =
(ImageViewTarget<GlideDrawable>) (ImageViewTarget) new DrawableImageViewTarget(imageView);
.into(target);
This is the only way I could make it compile because of the generic bounds on into(), which should be: Y extends Target<? super TranscodeType>, but it gets crazy inside Glide if it's changed.


Alternative: if you're willing to give up .crossFade() [you can still use .animate(android.R.anim.fade_in)] you can do .fromString().asBitmap().....into(imageView); because it creates a BitmapImageViewTarget.


I hope this helps with your problem too not just the flickr sample, and also that Sam knows a better solution without Target magic.
+Robi




Appendix: When I had scrolling problems I managed to work around them with the following class which eats the mentioned requestlayout:
public void ImageView.setImageDrawable(Drawable drawable) {
        ....
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}

public class RigidImageView extends ImageView {
public RigidImageView(Context context) {
super(context);
}
public RigidImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RigidImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

private boolean eatRequestLayout = false;

@Override public void setImageDrawable(Drawable drawable) {
eatRequestLayout = true;
super.setImageDrawable(drawable);
eatRequestLayout = false;
}

@Override public void requestLayout() {
if (!eatRequestLayout) {
super.requestLayout();
}
}

@Override public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm); // Calls through to setImageDrawable since Eclair
}

@Override public void setImageResource(int resId) {
throw new UnsupportedOperationException("Use setImageDrawable");
}

@Override public void setImageURI(Uri uri) {
throw new UnsupportedOperationException("Use setImageDrawable");
}
}


On Thu, Apr 30, 2015 at 12:11 AM, Andre Classen <acla...@hotmail.de> wrote:
Tested with dontAnimate() but the problem is still present.

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
Apr 30, 2015, 7:54:14 AM4/30/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com
Wow, thank you very much for your answer.

So, it is not 'that' problem for me.
We are just evaluating different libs to replace our self coded image loader.
Maybe i will find some time in the future to debug glide.
For now i'll just don't use glide.

Andre Classen

unread,
May 19, 2015, 8:45:40 PM5/19/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com
Me again ;)

I'm using a custom transfomation.

Seems to work pretty well, so far.
What do you think ?

protected Bitmap transform(BitmapPool bitmapPool, Bitmap toTransform, int width, int height) {
int dstWidth=(int) ((float)width*0.5f);
int dstHeight=(int) ((float)height*0.5f);
final float srcAspect = (float) toTransform.getWidth() / (float) toTransform.getHeight();
final float dstAspect = (float) dstWidth / (float) dstHeight;
Rect dst;
if (srcAspect > dstAspect) {
dst= new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect));
} else {
dst= new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight);
}
dst.offsetTo((width-dst.width())/2,(height-dst.height())/2);
Bitmap recycled = bitmapPool.get(width,height, toTransform.getConfig() != null ? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
final Bitmap result;
if (recycled != null) {
result = recycled;
} else {
result = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
}
result.setHasAlpha(toTransform.hasAlpha());
Canvas canvas = new Canvas(result);
canvas.drawARGB(255,255,255,255);
Rect src=new Rect(0,0,toTransform.getWidth(),toTransform.getHeight());
canvas.drawBitmap(toTransform, src, dst, new Paint(Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG));
if (recycled != null && recycled != result && !bitmapPool.put(recycled)) {
recycled.recycle();
}
return result;
}

Róbert Papp

unread,
May 20, 2015, 5:59:25 AM5/20/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet

:) that's a surprise, you said you won't be using it, welcome back!

Other than some micro-refactors/suggestions, it looks good to me. Here they are in order of importance:

1. performance
Use pool.getDirty when you'll draw over the whole image, like you do here with drawARGB(). (See doc of method)

2. dither
I don't think you need that paint flag, as I remember from its doc it is only useful if the source image has higher depth than the target, which should never happen in your case (because of fallback to 8888).

3. recycled
I couldn't think of a way that last recycle() will ever be called (maybe it's too early in the morning). As I see recycled is always either null or the same as result, so that last if is unnecessary, which also leads to recycled variable being unnecessary, so you can just initialise result from the pool and if that was null, create a new one so you always have a nonnull result.

4. final
It's confusing that only half of the variables are final, but none of them are reassigned. Either make all of them final or none (I prefer this one).

5. consistent formatting
You should execute a Format Code IDE action on your code :) You can have save actions in Eclipse so that it is automatic and IDEA should do it automatically too. It's still worth learning the shortcut for it and using it as often as CTRL+S.

Have a nice day!
+Robi

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
May 20, 2015, 6:18:27 AM5/20/15
to glidel...@googlegroups.com, francois...@gmail.com, acla...@hotmail.de
> :) that's a surprise, you said you won't be using it, welcome back!

I started to fall in love with glide :D


1. performance
2. dither
Thx, i will look into this

3. recycled
You are right, but isn't this in 'transformationutil.centercrop' exactly the same ?

4. final
5. consistent formatting
Of course , this snippet was just a try this night :D


So do you think this is a 'good' workaround ?

many thanks

Andre Classen

unread,
May 20, 2015, 6:38:53 AM5/20/15
to glidel...@googlegroups.com, francois...@gmail.com
I do the drawARGB() only to make the created bitmap not black.
Do you know a better solution for this ?

Róbert Papp

unread,
May 20, 2015, 6:39:31 AM5/20/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet

I think it's a little wasteful, but if you're really keen on implementing that requirement and it works, you've answered it yourself.

Did you try the solution/alternative from my detailed email?

As to centerCrop, I don't see any recycle calls in TransformUtils (v3.6.0). There is one in CenterCrop.transform, but that's because TransformUtils may or may not use the bitmap from pool (there are more possibilities there with more ifs). CenterCrop essentially treats TransformUtils as black box, so it can evolve more easily.

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Róbert Papp

unread,
May 20, 2015, 6:52:16 AM5/20/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet

It should be transparent based on LruBitmapPool.get. I think the problem here is that you want a transparent image as a result but you're using the config from source which is probably opaque because its source is a JPEG.

Try always using 8888:

Bitmap recycled = bitmapPool.get(width,height, Bitmap.Config.ARGB_8888);
...
result = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
...
result.setHasAlpha(true); // probably not necessary
...
//- canvas.drawARGB(255,255,255,255);

On 20 May 2015 12:38, "Andre Classen" <acla...@hotmail.de> wrote:
I do the drawARGB() only to make the created bitmap not black.
Do you know a better solution for this ?

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
May 20, 2015, 11:03:18 AM5/20/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com
First, thank you very much for your comments !!


> As to centerCrop, I don't see any recycle calls in TransformUtils (v3.6.0).
Sorry, my mistake. I'm talking about centercrop.java which calls centerCrop in the transformutils class.



> Did you try the solution/alternative from my detailed email?
Yes, working with asBitmap() works also, but I love the idea that glide will handle the animations.


>I think it's a little wasteful,
Why do you think so ?
Asking because performance matters :D

Sam Judd

unread,
May 20, 2015, 11:11:12 AM5/20/15
to Andre Classen, glidel...@googlegroups.com, François Blavoet
If you're expecting transparent pixels, Robi is correct, always ask for ARGB_8888 from the pool and create with ARGB_8888 if you don't get a Bitmap from the pool. Drawing a color onto the Bitmap you obtain from the pool shouldn't be necessary because we call clear pixels in Glide's BitmapPool when you call get(): https://github.com/bumptech/glide/blob/master/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java#L119. getDirty() doesn't clear pixels so if you're not going to write over the entire Bitmap, you want get().

Sam

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
May 20, 2015, 11:19:33 AM5/20/15
to glidel...@googlegroups.com, acla...@hotmail.de, francois...@gmail.com
Yep, got it.
I haven't recognized getDirty() before.
Just startet with Glide 2 days ago.

What is the prefered way doing this in case of performance ?
Doing a get() with transparent pixels or doing a getDirty() followed by a drawcolor?

Guys, your support is awesome !

Róbert Papp

unread,
May 20, 2015, 12:04:34 PM5/20/15
to Andre Classen, glidel...@googlegroups.com
> Did you try the solution/alternative from my detailed email?
Yes, working with asBitmap() works also, but I love the idea that glide will handle the animations.
You don't lose all animations, you only lose built-in support for crossFade (TransitionDrawable), you should be able to still use any number of animations, like .animate(android.R.anim.fade_in)

>I think it's a little wasteful,
Why do you think so ? 
It's simple really, you're allocating an image with 4 times more pixels than needed to hold your image, just to fill the remaining 3x pixels with transparent/white. But if there is know other way, that's a tradeoff.
Asking because performance matters :D
Every time I read this sentence, I have a stronger and stronger feeling that you're subscribed to Google Developers on YouTube.

What is the prefered way doing this in case of performance ?
Doing a get() with transparent pixels or doing a getDirty() followed by a drawcolor?
It really depends on what you're looking for:
  • If you want black (config without alpha channel) or transparent (configs with alpha channel) as background then use get(). 
  • If you will fully overwrite all pixels by draw*() calls, use getDirty()
  • If you want any other background color, use getDirty() [=previous] and then drawARGB or eraseColor
  • If you will draw something semi-transparent (e.g. Paint with alpha < 1), best bet is probably get()
  • In your case you want transparent background and will draw a smaller bitmap in the middle of the transparent area, so use get() and no drawARGB.
The only optimization with get/getDirty is that you don't overdraw (on the Bitmap), when you don't need to.
I could compare it to sorting a known-sorted array "just to be sure", which no sane programmer would do.

+Robi

--
You received this message because you are subscribed to the Google Groups "Glide" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glidelibrary...@googlegroups.com.
To post to this group, send email to glidel...@googlegroups.com.

Andre Classen

unread,
May 20, 2015, 12:12:06 PM5/20/15
to glidel...@googlegroups.com, acla...@hotmail.de
> It's simple really, you're allocating an image with 4 times more pixels than needed to hold your image, just to fill the remaining 3x pixels with transparent/white. But if there is know other way, that's a tradeoff.

I know :( But if I return a different sized image from my transform methods the 'bug' happens.


> Every time I read this sentence, I have a stronger and stronger feeling that you're subscribed to Google Developers on YouTube.
Hehe, you got it.

Reply all
Reply to author
Forward
0 new messages