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

Data URI scheme (e.g. Base64) #556

Closed
pullmann opened this issue Jul 29, 2015 · 23 comments
Closed

Data URI scheme (e.g. Base64) #556

pullmann opened this issue Jul 29, 2015 · 23 comments

Comments

@pullmann
Copy link

I need some help on implementing a data uri scheme image decoder, I get string containing images encoded in base64
with something like this: :
"data:image/jpeg;base64,/9j/4AAQSkZJ.....TY3OD=="
Is there an existing decoder or an example on how to implement it in glise?
I'm currently using a modified version of the "Displaying Bitmaps Efficiently" example, but the caching is really basic.

Thanks in Advance.
Roberto.

@TWiStErRob
Copy link
Collaborator

Can you please elaborate more on what are you trying to achieve, for example:

  • Where is that string coming from? (stream, deserialized [json] object, Cursor, RPC, etc...)
  • Do you have an object with getImage() which returns this or just a String?
  • Do you have an id for the image (may be needed for caching)? (file name, item ID, ...)
  • Is the ID stable? (Same ID means same image data)
  • Can the prefix vary? (E.g. data:image/png;base64 or data:image/jpeg;charset=utf8;base64)

@pullmann
Copy link
Author

Sorry if i wasn't clear enough, (english is not my first language)

Where is that string coming from? (stream, deserialized [json] object, Cursor, RPC, etc...)
Do you have an object with getImage() which returns this or just a String?

it's stored in a remote server (firebase), so it coud be a json object or a string stream.

Do you have an id for the image (may be needed for caching)? (file name, item ID, ...)
Is the ID stable? (Same ID means same image data)

Yes to both cases, its a unique stable id.

Can the prefix vary? (E.g. data:image/png;base64 or data:image/jpeg;charset=utf8;base64)

No. it's allways the same format.

What I'm currently doing is this:

  1. try to get the image from the cache
  2. if it's not in cache then download the string data, decode it into a bytearray and converting it to a jpeg and storing it in the cache.
    What I don't quite get is how to do that with glide. I want to take advantage of the rest of the library, specially the cache implementation.

@TWiStErRob
Copy link
Collaborator

You mentioned Firebase so I went ahead and checked out what it is, I've never used it, but hopefully I got it mostly right:

Glide
        .with(context)
        .load(new FirebaseImage("the-stable-id-you-mentioned"))
        .placeholder(android.R.color.darker_gray)
        .error(android.R.color.black)
        .into(imageView)
;
public class FirebaseImage {
    // final Firebase fb; // see FirebaseImageModelLoader ctor
    private final String key;
    public FirebaseImage(String key) {
        this.key = key;
    }

    public String getDataUri(Firebase fb) throws InterruptedException, FirebaseException {
        return (String)syncGetValue(fb.child("images").child(key));
    }

    private static Object syncGetValue(Firebase image) throws InterruptedException, FirebaseException {
        // hacky, but couldn't find better API :(
        final AtomicReference<Object> resultRef = new AtomicReference<>();
        final AtomicReference<FirebaseException> thrownRef = new AtomicReference<>();
        final CountDownLatch latch = new CountDownLatch(1);
        image.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override public void onDataChange(DataSnapshot snapshot) {
                resultRef.set(snapshot.getValue());
                latch.countDown();
            }
            @Override public void onCancelled(FirebaseError error) {
                thrownRef.set(error.toException());
                latch.countDown();
            }
        });
        latch.await();
        FirebaseException thrown = thrownRef.get();
        Object result = resultRef.get();
        if (thrown != null) {
            throw thrown;
        } else if (result == null) {
            throw new NullPointerException("No data returned from " + image);
        } else {
            return result;
        }
    }
}

// TODO https://github.com/bumptech/glide/wiki/Configuration#creating-a-glidemodule
public class FirebaseGlideModule implements GlideModule {
    @Override public void applyOptions(Context context, GlideBuilder builder) {

    }
    @Override public void registerComponents(Context context, Glide glide) {
        Firebase fb = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
        fb.authWithPassword("", "", null); // TODO fire auth to background if needed, see FirebaseImageModelLoader ctor
        glide.register(FirebaseImage.class, InputStream.class, new FirebaseImageModelLoader.Factory(fb));
    }
}

// TODO read https://github.com/bumptech/glide/wiki/Downloading-custom-sizes-with-Glide
class FirebaseImageModelLoader implements StreamModelLoader<FirebaseImage> {
    private final Firebase fb;
    public FirebaseImageModelLoader(Firebase fb) {
        this.fb = fb; // TODO *alternatively* add a Firebase field to FirebaseImage and use that from model in DataFetcher
    }

    @Override public DataFetcher<InputStream> getResourceFetcher(final FirebaseImage model, int width, int height) {
        return new DataFetcher<InputStream>() {
            private Thread decodingThread;
            @Override public InputStream loadData(Priority priority) throws Exception {
                decodingThread = Thread.currentThread();
                // somehow make sure that fb is ready after authWithPassword is called
                String dataUri = model.getDataUri(fb);
                String base64 = dataUri.substring("data:image/jpeg;base64,".length());
                // depending on how you encoded it, the below is just a guess:
                // here the full base64 is decoded into bytes and the bytes will be parsed by Glide
                return new ByteArrayInputStream(Base64.decode(base64, Base64.NO_WRAP | Base64.URL_SAFE));
                // if you don't want to delay decoding you can use something like:
                // Base64InputStream (http://stackoverflow.com/a/19981216/253468)
                // here the base64 bytes are passed to Base64InputStream and that to Glide
                // so base64 decoding will be done later when Glide reads from the stream.
                return new Base64InputStream(new ByteArrayInputStream(base64.getBytes("utf-8")), Base64.NO_WRAP | Base64.URL_SAFE);
            }
            @Override public String getId() {
                return model.key; // for cache
            }
            @Override public void cancel() {
                // don't do this if it's likely to display in the future (read doc of cancel)
                decodingThread.interrupt(); // should fly out from await()
                // note that this may be not the correct way to cancel, just an example
                // Glide may not like interrupted threads in its pool
                // this may cause problems in future loads which is not good, test it
            }
            @Override public void cleanup() {
                decodingThread = null;
            }
        };
    }

    public static class Factory implements ModelLoaderFactory<FirebaseImage, InputStream> {
        private final Firebase fb;
        public Factory(Firebase fb) {
            this.fb = fb;
        }
        @Override public ModelLoader<FirebaseImage, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new FirebaseImageModelLoader(fb);
        }
        @Override public void teardown() {
            // only called if Glide.register is called with same first 2 args again
            fb.onDisconnect().cancel(); // TODO or whatever you call to disconnect
        }
    }
}

Please note that I only tested the Glide part, I don't have Firebase. So you have to make sure that dataUri will contain what is expected however you are accessing that from Firebase. loadData is in the background so we can do long running things and even block the Thread temporarily, careful about memory consumption and battery though.

@pullmann
Copy link
Author

Thank you very much!!! that's amazingly helpfull!!
I'll test it during the weekend and let you know how it works

Roberto

@sjudd sjudd added the question label Aug 4, 2015
@sjudd
Copy link
Collaborator

sjudd commented Aug 4, 2015

Thanks @TWiStErRob!

@sjudd sjudd closed this as completed Aug 4, 2015
@TWiStErRob
Copy link
Collaborator

@sjudd couldn't we incorporate this somehow into StringLoader so it's handled by default? At least for simple data Uris.

@sjudd
Copy link
Collaborator

sjudd commented Aug 4, 2015

Do you mean firebase? or a base 64 image string?

@TWiStErRob
Copy link
Collaborator

Just data URIs. As used in CSS or <IMG src=...

@sjudd sjudd added enhancement and removed question labels Aug 4, 2015
@sjudd sjudd reopened this Aug 4, 2015
@sjudd
Copy link
Collaborator

sjudd commented Aug 4, 2015

Ah I see what you mean, sure we could look in to that, it probably wouldn't be super hard.

@ReyesMagos
Copy link

Hi I had and image encode as string base 64, can I load this image with glide ?

@TWiStErRob
Copy link
Collaborator

@ReyesMagos You can do the same as above just have replace FirebaseImage with class Base64Image { String encoded; } and skip the Firebase instances, params and fields everywhere.

@TWiStErRob
Copy link
Collaborator

@pullmann did it work in the end? Please share with future readers of this issue.

@pullmann
Copy link
Author

@TWiStErRob , I saw the issue closed so I didn't comment on it, the FirebaseImage model worked like a charm, almost copyed and pasted your code, just replaced the firebase urls.

@TWiStErRob
Copy link
Collaborator

Glad to hear, thanks.

My views: Don't worry about closed issues, the worst that can happen is that you get no response (I've experienced this in other trackers and try to no do it here). For example I use close to signify "our work is done here, we think there's enough info provided". This is to keep the issue tracker cleaner, don't let solved issues linger as open; lot of times people only write back when there's another follow-up issue, not when it's working. Obviously we can reopen or continue discussion as needed. I'm pretty sure even a "closed as wontfix" can turn around with a simple sentence that brings in new information and destroys all the arguments before it. Communications FTW!

@sugarmanz
Copy link

sugarmanz commented May 13, 2016

Hello. I am looking to use glide to load a base64 encoded image that is packed in json which is downloaded from a given url. As mentioned above to @ReyesMagos, this is possible using the example above without the Firebase references, but I am having trouble separating out the Firebase references and keeping what is actually necessary for this to happen. I would like to use glide to handle the network request as well, but I cannot figure out where I would put it in the above example. Thanks for any help you might be able to provide.

EDIT: I forgot to mention that the specified url requires a json struct containing an image 'key'.

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

@sugarmanz see above commit for some examples, each package is a different approach. In your use case you have to make the request based on the image key, but Glide can cache the key->image if you want.

@sugarmanz
Copy link

@TWiStErRob That's exactly what I needed. Great library by the way.

@myhcqgithub
Copy link

@TWiStErRob Hello, my server returned data is base64 format How do I use the glide, need and hope to answer thanks

@TWiStErRob
Copy link
Collaborator

@TWiStErRob TWiStErRob changed the title Data URI scheme Data URI scheme (Base64) Apr 9, 2017
@TWiStErRob TWiStErRob changed the title Data URI scheme (Base64) Data URI scheme (e.g. Base64) Apr 9, 2017
@therajanmaurya
Copy link

therajanmaurya commented Oct 1, 2017

@TWiStErRob I saw the above comments but unable to solve my issue. Here, what I am trying to achieve.

I have image on server and while making GET request I need to add token and some other parameters in header but I am not getting image successfully and getting log like this

D/skia: --- SkImageDecoder::Factory returned null
D/skia: --- SkImageDecoder::Factory returned null

Here is the apparch I am doing

public class ImageLoaderUtils {

    private Context context;
    private BaseURL baseURL;

    public ImageLoaderUtils(Context Context context) {
        this.context = context;
        baseURL = new BaseURL();
    }

    private String buildUserImageUrl(long clientId) {
        return baseURL.getUrl() + ApiEndPoints.CLIENTS + "/" + clientId + "/images";
    }

    private GlideUrl buildGlideUrl(String imageUrl) {
        return new GlideUrl(imageUrl, new LazyHeaders.Builder()
                .addHeader(SelfServiceInterceptor.HEADER_TENANT, "default")
                .addHeader(SelfServiceInterceptor.HEADER_AUTH, "token...")
                .addHeader(SelfServiceInterceptor.HEADER_ACCEPT, "text/plain")
                .build());
    }

    public void loadUserProfileImage(final CircularImageView imageView) {
        Glide.with(context)
                .load(buildGlideUrl(buildUserImageUrl(id)))
                .asBitmap()
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .skipMemoryCache(true)
                .placeholder(R.drawable.ic_person_black_24dp)
                .error(R.drawable.ic_person_black_24dp)
                .centerCrop()
                .into(imageView);
    }
}

I know the response type with prefix data:image/png;base64 that will be https://gist.github.com/therajanmaurya/972ac0173edb8511c7c96e1b7b2b7398.

I am using glide version 3.7.0

@guilhermesgb
Copy link

For people using Glide 4.x, referring to this document in their official docs might be useful.

@guilhermesgb
Copy link

So, I actually just read this in the documentation (linked above):

As it turns out, Glide already supports Data URIs, so no need to do anything if you want load base64 strings as data URIs.

I wonder why it's not working in my case, then... I get this crash:

W/Glide: Load failed for data:image/png;base64,<encoded image data omitted for clarity> with size [936x480]
         class com.bumptech.glide.load.engine.GlideException: Failed to load resource
           Cause (1 of 1): class com.bumptech.glide.load.engine.GlideException: Failed LoadPath{StringUri->Object->Drawable}, LOCAL
             Cause (1 of 2): class com.bumptech.glide.load.engine.GlideException: Failed DecodePath{StringUri->Drawable->Drawable}
             Cause (2 of 2): class com.bumptech.glide.load.engine.GlideException: Failed DecodePath{StringUri->Bitmap->Drawable}

@guilhermesgb
Copy link

guilhermesgb commented Feb 2, 2018

So... just reporting here that I did figure out the issue: if I feed Glide with a Uri instead of raw String, it somehow won't give its DataUrl (Base64) decoder a try. Passing raw Strings solved the issue. Working on latest release (4.6.1).

@sjudd sjudd closed this as completed in 17c1c3d Feb 5, 2018
aw691190716 pushed a commit to aw691190716/glide that referenced this issue Feb 8, 2018
Previously we only supported data uris if they were provided as Strings.

Fixes bumptech#556.
sjudd added a commit to sjudd/glide that referenced this issue Mar 6, 2018
-------------
Include the debug aar in release artifacts for Android projects.

We removed the release variant a while ago to speed up the build, which
has the side affect of removing the release aar from artifacts. Since
we expect the debug and release variants to be identical (hence why
we disabled the release variant), it should be safe to just use the
debug aar instead. We will have to specify it explicitly since android’s
rules unsurprisingly only add the release variant by default.

-------------
Bump version to 4.6.0

-------------
Update readme to 4.6.0

Also removes the old v4 dependency from maven deps, I don’t think it’s
necessary.

-------------
Change update_javadocs to use debugJavadocJar instead of release.

We’ve disabled the release variant.

-------------
Bump version to 4.7.0-SNAPSHOT

-------------
Add POM dependencies explicitly.

Fixes bumptech#2863.

-------------
Bump version to 4.6.1

-------------
Update readme to 4.6.1

-------------
Fix param mistake (bumptech#2873)

-------------
Update SimpleTarget javadoc to match v4 API.

-------------
Add javadoc for RequestOptions.apply/RequestBuilder.apply.

Related to bumptech#2858.

-------------
Add support for Uri data uris.

Previously we only supported data uris if they were provided as Strings.

Fixes bumptech#556.

-------------
Make GlideBuilder.build package private.

It shouldn’t have been made visible and can’t
safely be used directly outside of the library.

Fixes bumptech#2885

-------------
Handle notifications in MultiFetcher after cancellation.

We can’t guarantee that every fetcher implementation will strictly avoid
notifying after they’ve been cancelled.

This also improves the behavior in VolleyStreamFetcher so that it attempts to avoid notifying after cancel, although it still doesn’t make
any strict guarantee.

Fixes bumptech#2879

-------------
Re-enable -Werror for java compilation.

Related to bumptech#2886.

-------------
Fix a deprecation warning in DataUriTest.

-------------
gradle 4.5.1

-------------
deprecate fragments

-------------
add @deprecated to javadoc and suppress deprecations in code

-------------
Created by MOE: https://github.com/google/moe

MOE_MIGRATED_REVID=99229725401d5777e059da7b6331134bf73fbcdf

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=185535564
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants