Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use already locally available Drawable/Bitmap as thumbnail for animation #1406

Closed
BobOtike opened this issue Aug 11, 2016 · 13 comments
Closed
Labels

Comments

@BobOtike
Copy link

@TWiStErRob I have a single ImageView that cycles through different images. I want the transition to be animated no matter what and already found your solution provided here #527.

The solution works well, but not all images I want to display need to be loaded from a url. Some are already locally available, as Drawable or Bitmap (Drawable comes from 3rd party lib and is not a resource I can simply use the id of). My idea was to write a custom ModelLoader so I can build a Glide load for the thumbnail from this Bitmap/Drawable, but I have no idea how to go about this. Basically there is no loading needed by Glide, I only want to supply it my Drawable/Bitmap (whatever is easier, needs less resources, ...) to use as thumbnail so I can have consistent animations.

@TWiStErRob
Copy link
Collaborator

TWiStErRob commented Aug 11, 2016

You might be interested in #861 (comment) and #122 (comment).

Bitmaps are easy to deal with because you can just new BitmapDrawable(bitmap, ...), so what you need is a generic solution that works with all kinds of Drawables.

Drawable comes from 3rd party lib and is not a resource I can simply use the id of

There must be something that helps you generate that Drawable, for example if you use new ThirdPartyObject(hello).dump(world) then your model to write a model loader for is class ThirdParty { X hello; Y world; }, you can pass that through the loader and fetcher and let the decoder do the above dump call. From there use a UnitTranscoder and use into(new ImageViewTarget<Drawable>() {...}). Check out how SVG works in the samples and take a look at the workflow to hopefully better understand what I just said.


Mix-and matching between from Url and direct-from Drawable is hard. If you need more help with that I would need more concrete code and types.

@TWiStErRob
Copy link
Collaborator

Another thing that may help is placeholder instead of thumbnail for Drawables, however that probably only works if you have an array of Drawable's to cycle through.

@BobOtike
Copy link
Author

then your model to write a model loader for is class ThirdParty { X hello; Y world; }

My confusion is not about what constitutes as the model but about all the different steps (loader, fetcher, decoder, ...) and which of those I need to alter in which way to achieve my goal. Going through the samples and the links you provided hopefully sheds more light on that for me.

Thanks for the hints, I will look through them and get back to you with my succes or need of more help.

@BobOtike
Copy link
Author

I just re-read your first comment and noticed that there was a misunderstanding: the local images from the 3rd party lib are available to me as Bitmap or Drawable, I can choose which one I want to get. I stated both types because I don't really care which I use. So what I need is a solution that works either with Drawables or Bitmaps, not a generic solution for all kinds of Drawables.

@TWiStErRob
Copy link
Collaborator

Everything still stands. My point was that the best is to use Drawables everywhere, because that way it's more generic. This genericness helps to combine "images" from multiple sources, be it generated Drawable or simply a remote JPEG, even a local animated GIF. If you check the SVG sample you'll see that there's no Bitmap backing the drawable, it's a special implementation. Of course you can rasterize any Drawable into a Bitmap, but it might be more performant to not do that.

I think you should try to solve it with Drawables first, and if it doesn't work for some reason only then resort to Bitmaps.

@BobOtike
Copy link
Author

Sure, this didn't invalidate anything you said, just wanted to point out the misunderstanding :)

Alright, I will try to put something together for Drawables and see where I end up.

@BobOtike
Copy link
Author

BobOtike commented Aug 12, 2016

I have started to implement the needed components according to your proof of concept and encountered two problems already:

  • thumbnail expects a `GenericRequestBuilder<?, ?, ?, GlideDrawable>, how can I convert my Drawable into a GlideDrawable?
  • DrawableResource#get returns drawable.getConstantState().newDrawable() and for some reason, getConstantState always returns null for the 3rd party Drawables, any idea why that might be and how to circumvent the issue?

@TWiStErRob
Copy link
Collaborator

TWiStErRob commented Aug 12, 2016

Hmm, if the third party lib doesn't implement the Android contract for Drawable, this won't work. As a workaround you can wrap it in your own drawable that handles everything correctly. Delegate most of the functions, but have a correct ConstantState.

You don't convert your Drawable into GlideDrawable, you need to lose the type information to have a Drawable based load:

Glide
    .with(this)
    .using(new StreamStringLoader(context), InputStream.class)
    .from(String.class)
    .as(GifBitmapWrapper.class)
    .transcode(new ResourceTranscoder<GifBitmapWrapper, Drawable>() {
        private final ResourceTranscoder<GifBitmapWrapper, GlideDrawable> transcoder =
                new GifBitmapWrapperDrawableTranscoder(new GlideBitmapDrawableTranscoder(context));
        @SuppressWarnings("unchecked")
        @Override public Resource<Drawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
            // should be safe because we're upcasting the generic type and Resource only has outgoing generics
            return (Resource)transcoder.transcode(toTranscode);
        }
        @Override public String getId() {
            return transcoder.getId();
        }
    }, Drawable.class)
    // you may need to specify decoder, cacheDecoder, etc. manually;
    // base them on ImageVideoGifDrawableLoadProvider, ImageVideoDataLoadProvider, StreamBitmapDataLoadProvider
    .load(url)
    .into(new ImageViewTarget<Drawable>(imageView) {
        // may need to copy more from GlideDrawableImageViewTarget with instanceof, if you want to play GIF
        @Override protected void setResource(Drawable resource) {
            view.setImageDrawable(resource);
        }
    })
;

@BobOtike
Copy link
Author

BobOtike commented Aug 12, 2016

Okay, I'm feeling that I've not quite gotten the hang of it yet. Let me show you what I've done so far so you can give me more precise advise on its correctness.

These are the RequestBuilders:

GenericRequestBuilder<String, InputStream, Drawable, Drawable> urlGlide = Glide.with(this)
                .using(new StreamStringLoader(getActivity()), InputStream.class)
                .from(String.class)
                .as(Drawable.class)
                .sourceEncoder(new StreamEncoder())
                .cacheDecoder(new FileToStreamDecoder<>(new ResourceDecoder<InputStream, Drawable>() {
                    @Override
                    public Resource<Drawable> decode(InputStream source, int width, int height) throws IOException {
                        return new SimpleResource<>(Drawable.createFromStream(source, source.toString()));
                    }
                    @Override
                    public String getId() {
                        return "";
                    }
                }))
                .diskCacheStrategy(DiskCacheStrategy.SOURCE);


GenericRequestBuilder<Drawable, Drawable, Drawable, Drawable> drawableGlide = Glide.with(this)
                .using(new PassthroughModelLoader<>(), Drawable.class)
                .from(Drawable.class)
                .as(Drawable.class)
                .decoder(new DrawableResourceDecoder())
                .diskCacheStrategy(DiskCacheStrategy.NONE);
                .skipMemoryCache(true)

The used classes:

public class PassthroughModelLoader<Result, Source extends Result> implements ModelLoader<Source, Result> {
    @Override
    public DataFetcher<Result> getResourceFetcher(Source model, int width, int height) {
        return new CastingDataFetcher<>(model);
    }


    private static class CastingDataFetcher<Source, Result> implements DataFetcher<Result> {

        private final Source model;

        public CastingDataFetcher(Source model) {
            this.model = model;
        }
        @Override
        @SuppressWarnings("unchecked")
        public Result loadData(Priority priority) throws Exception {
            return (Result) model;
        }
        @Override
        public void cleanup() {

        }
        @Override
        public String getId() {
            return "";
        }
        @Override
        public void cancel() {

        }
    }
}

public class DrawableResourceDecoder implements ResourceDecoder<Drawable, Drawable> {
    @Override
    public Resource<Drawable> decode(Drawable source, int width, int height) throws IOException {
        return new DrawableResource<Drawable>(source) {
            @Override
            public int getSize() {
                return 1;
            }
            @Override
            public void recycle() {

            }
        };
    }
    @Override
    public String getId() {
        return getClass().getSimpleName();
    }
}

The load line if I want to go from Drawable to url-loaded image:

urlGlide.clone()
        .load(url)
        .thumbnail(drawableGlide.clone()
            .load(lastDrawable)
        .listener(new RequestListener<String, Drawable>() {
            @Override
            public boolean onException(Exception e, String model, Target<Drawable> target, boolean isFirstResource) {
                return false;
            }
            @Override
            public boolean onResourceReady(Drawable resource, String model, Target<Drawable> target, boolean isFromMemoryCache, boolean isFirstResource) {

                return !isFirstResource && new DrawableCrossFadeFactory<>()
                        .build(false, false)
                        .animate(resource, (GlideAnimation.ViewAdapter) target);
            }
        })
        .into(imageView);

lastUrl = url;

The load line if I want to go from url-loaded image to Drawable:

drawableGlide.clone()
    .load(drawable)
    .thumbnail(urlGlide.clone()
        .load(lastUrl))
    .listener(new RequestListener<Drawable, Drawable>() {
        @Override
        public boolean onException(Exception e, Drawable model, Target<Drawable> target, boolean isFirstResource) {
            return false;
        }
        @Override
        public boolean onResourceReady(Drawable resource, Drawable model, Target<Drawable> target, boolean isFromMemoryCache, boolean isFirstResource) {

            return !isFirstResource && new DrawableCrossFadeFactory<>()
                    .build(false, false)
                    .animate(resource, (GlideAnimation.ViewAdapter) target);
        }
    }
    .into(imageView);

lastDrawable = drawable;

TWiStErRob added a commit to TWiStErRob/glide-support that referenced this issue Aug 12, 2016
@TWiStErRob
Copy link
Collaborator

See above commit. Ask something is not clear.

@BobOtike
Copy link
Author

BobOtike commented Aug 13, 2016

Wow, thank you so much for giving me this example to work with!

I am in the process of going through the example, but I get an error at .animate(new AlwaysCrossFade()). The method animate that takes a GlideAnimationFactory as argument in GenericRequestBuilder seems to have package-local access?

@TWiStErRob
Copy link
Collaborator

Yes, that was/will be published in 3.8.0, that sample uses the -SNAPSHOT (see build.gradle). It much easier to interact with animations after that's public, that listener solution feels really hacky.

@BobOtike
Copy link
Author

I've got everything up and running now and looks like it works as expected. Thank you for assisting me in finding the solution, I don't think I would have managed this on my own, especially the GifBitmapWrapper-part of you first code example had me confused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants