Image

image

In order to display images in your markdown ImagesPlugin can be used.

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create())
        .build();

TIP

There are also modules that add image loading capabilities to markdown based on image-loading libraries: image-glide and image-picasso

ImagesPlugin splits the image-loading into 2 parts: scheme-handling and media-decoding.

SchemeHandler

To add a scheme-handler to ImagesPlugin:

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create())
        .usePlugin(new AbstractMarkwonPlugin() {
            @Override
            public void configure(@NonNull Registry registry) {
                registry.require(ImagesPlugin.class, new Action<ImagesPlugin>() {
                    @Override
                    public void apply(@NonNull ImagesPlugin imagesPlugin) {
                        imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create());
                    }
                });
            }
        })
        .build();
final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                plugin.addSchemeHandler(DataUriSchemeHandler.create());
            }
        }))
        .build();

ImagesPlugin comes with a set of predefined scheme-handlers:

  • FileSchemeHandler - file://
  • DataUriSchemeHandler - data:
  • NetworkSchemeHandler - http, https
  • OkHttpNetworkSchemeHandler - http, https

FileSchemeHandler

Loads images via file:// scheme. Allows loading images from assets folder.

// default implementation, no assets handling
FileSchemeHandler.create();

// assets loading
FileSchemeHandler.createWithAssets(context);

WARNING

Assets loading will work when your URL will include android_asset in the path, for example: file:///android_asset/image.png (mind the 3 slashes ///). If you wish to assume all images without proper scheme to point to assets folder, then you can use UrlProcessorAndroidAssets

By default ImagesPlugin includes plain FileSchemeHandler (without assets support), so if you wish to change that you can explicitly specify it:

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context));
            }
        }))
        .build();

DataUriSchemeHandler

DataUriSchemeHandler allows inlining images with data: scheme (data:image/svg+xml;base64,MTIz). This scheme-handler is registered by default, so you do not need to add it explicitly.

NetworkSchemeHandler

NetworkSchemeHandler allows obtaining images from http:// and https:// uris (internally it uses HttpURLConnection). This scheme-handler is registered by default

OkHttpNetworkSchemeHandler

OkHttpNetworkSchemeHandler allows obtaining images from http:// and https:// uris via okhttp library. Please note that in order to use this scheme-handler you must explicitly add okhttp library to your project.

// default instance
OkHttpNetworkSchemeHandler.create();

// specify OkHttpClient to use
OkHttpNetworkSchemeHandler.create(new OkHttpClient());

// @since 4.0.0
OkHttpNetworkSchemeHandler.create(Call.Factory);

Custom SchemeHandler

public abstract class SchemeHandler {

    @NonNull
    public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);

    @NonNull
    public abstract Collection<String> supportedSchemes();
}

Starting with 4.0.0 SchemeHandler can return a result (when no further decoding is required):

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                // for example to return a drawable resource
                plugin.addSchemeHandler(new SchemeHandler() {
                    @NonNull
                    @Override
                    public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {

                        // will handle URLs like `drawable://ic_account_24dp_white`
                        final int resourceId = context.getResources().getIdentifier(
                                raw.substring("drawable://".length()),
                                "drawable",
                                context.getPackageName());

                        // it's fine if it throws, async-image-loader will catch exception
                        final Drawable drawable = context.getDrawable(resourceId);

                        return ImageItem.withResult(drawable);
                    }

                    @NonNull
                    @Override
                    public Collection<String> supportedSchemes() {
                        return Collections.singleton("drawable");
                    }
                });
            }
        }))
        .build();

Otherwise SchemeHandler must return an InputStream with proper content-type information for further processing by a MediaDecoder:

imagesPlugin.addSchemeHandler(new SchemeHandler() {
    @NonNull
    @Override
    public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
        return ImageItem.withDecodingNeeded("image/png", load(raw));
    }

    @NonNull
    private InputStream load(@NonNull String raw) {...}
});

MediaDecoder

ImagesPlugin comes with predefined media-decoders:

  • GifMediaDecoder adds support for GIF
  • SvgMediaDecoder adds support for SVG
  • DefaultMediaDecoder

WARNING

If you wish to add support for SVG or GIF you must explicitly add these dependencies to your project:

For security reasons it's advisable to use latest versions of these libraries. If you notice compilation and/or runtime issues when used with Markwon, please create an issue specifying library and library version used.

GifMediaDecoder

Adds support for GIF media in markdown. If pl.droidsonroids.gif:android-gif-drawable:* dependency is found in the classpath, then registration will happen automatically.

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                // autoplayGif controls if GIF should be automatically started
                plugin.addMediaDecoder(GifMediaDecoder.create(/*autoplayGif*/false));
            }
        }))
        .build();

SvgMediaDecoder

Adds support for SVG media in markdown. If com.caverock:androidsvg:* dependency is found in the classpath, then registration will happen automatically.

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {

                // uses supplied Resources
                plugin.addMediaDecoder(SvgMediaDecoder.create(context.getResources()));

                // uses Resources.getSystem()
                plugin.addMediaDecoder(SvgMediaDecoder.create());
            }
        }))
        .build();

DefaultMediaDecoder

DefaultMediaDecoder tries to decode supplied InputStream as Bitmap (via BitmapFactory.decodeStream(inputStream)). This decoder is registered automatically.

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                
                // uses supplied Resources
                plugin.defaultMediaDecoder(DefaultMediaDecoder.create(context.getResources()));
                
                // uses Resources.getSystem()
                plugin.defaultMediaDecoder(DefaultMediaDecoder.create());
            }
        }))
        .build();

AsyncDrawableScheduler

AsyncDrawableScheduler is used in order to give AsyncDrawable a way to invalidate TextView that is holding it. A plugin that is dealing with AsyncDrawable should always call these methods:

final Markwon markwon = Markwon.builder(context)
        .usePlugin(new AbstractMarkwonPlugin() {
            @Override
            public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
                AsyncDrawableScheduler.unschedule(textView);
            }

            @Override
            public void afterSetText(@NonNull TextView textView) {
                AsyncDrawableScheduler.schedule(textView);
            }
        })
        .build();

TIP

Starting with 4.0.0 multiple plugins can call AsyncDrawableScheduler#schedule method without the penalty to process AsyncDrawable callbacks multiple times (internally caches state which ensures that a TextView is processed only once the text has changed).

ErrorHandler

An ErrorHandler can be used to receive an error that has happened during image loading and (optionally) return an error drawable to be displayed instead

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                plugin.errorHandler(new ImagesPlugin.ErrorHandler() {
                    @Nullable
                    @Override
                    public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) {
                        return null;
                    }
                });
            }
        }))
        .build();

PlaceholderProvider

To display a placeholder during image loading PlaceholderProvider can be used:

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
                    @Nullable
                    @Override
                    public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
                        return null;
                    }
                });
            }
        }))
        .build();

TIP

If your placeholder drawable has specific size which is not the same an image that is being loaded, you can manually assign bounds to the placeholder:

plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
    @Override
    public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
        final ColorDrawable placeholder = new ColorDrawable(Color.BLUE);
        // these bounds will be used to display a placeholder,
        // so even if loading image has size `width=100%`, placeholder
        // bounds won't be affected by it
        placeholder.setBounds(0, 0, 48, 48);
        return placeholder;
    }
});

TIP

If you are using html you do not have to additionally setup images displayed via <img> tag, as HtmlPlugin automatically uses configured image loader. But images referenced in HTML come with additional support for sizes, which is not supported natively by markdown, allowing absolute or relative sizes:

<img src="./assets/my-image" width="100%">
Last Updated: 7/16/2020, 12:14:12 PM