Introduce MediaDecoder abstraction for image-loader module
This commit is contained in:
parent
4a80616f75
commit
146ba9c575
@ -15,6 +15,9 @@ import dagger.Provides;
|
|||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import ru.noties.markwon.il.AsyncDrawableLoader;
|
import ru.noties.markwon.il.AsyncDrawableLoader;
|
||||||
|
import ru.noties.markwon.il.GifMediaDecoder;
|
||||||
|
import ru.noties.markwon.il.ImageMediaDecoder;
|
||||||
|
import ru.noties.markwon.il.SvgMediaDecoder;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
@ -46,7 +49,7 @@ class AppModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
OkHttpClient client() {
|
OkHttpClient client() {
|
||||||
return new OkHttpClient.Builder()
|
return new OkHttpClient.Builder()
|
||||||
.cache(new Cache(app.getCacheDir(), 1024L * 20))
|
.cache(new Cache(app.getCacheDir(), 1024L * 1024 * 20)) // 20 mb
|
||||||
.followRedirects(true)
|
.followRedirects(true)
|
||||||
.retryOnConnectionFailure(true)
|
.retryOnConnectionFailure(true)
|
||||||
.build();
|
.build();
|
||||||
@ -79,7 +82,11 @@ class AppModule {
|
|||||||
.client(client)
|
.client(client)
|
||||||
.executorService(executorService)
|
.executorService(executorService)
|
||||||
.resources(resources)
|
.resources(resources)
|
||||||
.autoPlayGif(false)
|
.mediaDecoders(
|
||||||
|
SvgMediaDecoder.create(resources),
|
||||||
|
GifMediaDecoder.create(false),
|
||||||
|
ImageMediaDecoder.create(resources)
|
||||||
|
)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
package ru.noties.markwon.il;
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.caverock.androidsvg.SVG;
|
|
||||||
import com.caverock.androidsvg.SVGParseException;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -34,22 +28,21 @@ import okhttp3.OkHttpClient;
|
|||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
|
|
||||||
public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static AsyncDrawableLoader create() {
|
public static AsyncDrawableLoader create() {
|
||||||
return builder().build();
|
return builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static AsyncDrawableLoader.Builder builder() {
|
public static AsyncDrawableLoader.Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String HEADER_CONTENT_TYPE = "Content-Type";
|
private static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||||
private static final String CONTENT_TYPE_SVG = "image/svg+xml";
|
|
||||||
private static final String CONTENT_TYPE_GIF = "image/gif";
|
|
||||||
|
|
||||||
private static final String FILE_ANDROID_ASSETS = "android_asset";
|
private static final String FILE_ANDROID_ASSETS = "android_asset";
|
||||||
|
|
||||||
@ -58,9 +51,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private final Handler mainThread;
|
private final Handler mainThread;
|
||||||
private final Drawable errorDrawable;
|
private final Drawable errorDrawable;
|
||||||
|
private final List<MediaDecoder> mediaDecoders;
|
||||||
// @since 1.1.0
|
|
||||||
private final boolean autoPlayGif;
|
|
||||||
|
|
||||||
private final Map<String, Future<?>> requests;
|
private final Map<String, Future<?>> requests;
|
||||||
|
|
||||||
@ -70,7 +61,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
this.executorService = builder.executorService;
|
this.executorService = builder.executorService;
|
||||||
this.mainThread = new Handler(Looper.getMainLooper());
|
this.mainThread = new Handler(Looper.getMainLooper());
|
||||||
this.errorDrawable = builder.errorDrawable;
|
this.errorDrawable = builder.errorDrawable;
|
||||||
this.autoPlayGif = builder.autoPlayGif;
|
this.mediaDecoders = builder.mediaDecoders;
|
||||||
this.requests = new HashMap<>(3);
|
this.requests = new HashMap<>(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,12 +100,15 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
final Item item;
|
final Item item;
|
||||||
|
final boolean isFromFile;
|
||||||
|
|
||||||
final Uri uri = Uri.parse(destination);
|
final Uri uri = Uri.parse(destination);
|
||||||
if ("file".equals(uri.getScheme())) {
|
if ("file".equals(uri.getScheme())) {
|
||||||
item = fromFile(uri);
|
item = fromFile(uri);
|
||||||
|
isFromFile = true;
|
||||||
} else {
|
} else {
|
||||||
item = fromNetwork(destination);
|
item = fromNetwork(destination);
|
||||||
|
isFromFile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable result = null;
|
Drawable result = null;
|
||||||
@ -122,13 +116,15 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
if (item != null
|
if (item != null
|
||||||
&& item.inputStream != null) {
|
&& item.inputStream != null) {
|
||||||
try {
|
try {
|
||||||
if (CONTENT_TYPE_SVG.equals(item.type)) {
|
|
||||||
result = handleSvg(item.inputStream);
|
final MediaDecoder mediaDecoder = isFromFile
|
||||||
} else if (CONTENT_TYPE_GIF.equals(item.type)) {
|
? mediaDecoderFromFile(item.fileName)
|
||||||
result = handleGif(item.inputStream);
|
: mediaDecoderFromContentType(item.contentType);
|
||||||
} else {
|
|
||||||
result = handleSimple(item.inputStream);
|
if (mediaDecoder != null) {
|
||||||
|
result = mediaDecoder.decode(item.inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
item.inputStream.close();
|
item.inputStream.close();
|
||||||
@ -161,7 +157,8 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Item fromFile(Uri uri) {
|
@Nullable
|
||||||
|
private Item fromFile(@NonNull Uri uri) {
|
||||||
|
|
||||||
final List<String> segments = uri.getPathSegments();
|
final List<String> segments = uri.getPathSegments();
|
||||||
if (segments == null
|
if (segments == null
|
||||||
@ -171,19 +168,10 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Item out;
|
final Item out;
|
||||||
final String type;
|
|
||||||
final InputStream inputStream;
|
final InputStream inputStream;
|
||||||
|
|
||||||
final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0));
|
final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0));
|
||||||
final String lastSegment = uri.getLastPathSegment();
|
final String fileName = uri.getLastPathSegment();
|
||||||
|
|
||||||
if (lastSegment.endsWith(".svg")) {
|
|
||||||
type = CONTENT_TYPE_SVG;
|
|
||||||
} else if (lastSegment.endsWith(".gif")) {
|
|
||||||
type = CONTENT_TYPE_GIF;
|
|
||||||
} else {
|
|
||||||
type = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assets) {
|
if (assets) {
|
||||||
final StringBuilder path = new StringBuilder();
|
final StringBuilder path = new StringBuilder();
|
||||||
@ -212,7 +200,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
out = new Item(type, inputStream);
|
out = new Item(fileName, null, inputStream);
|
||||||
} else {
|
} else {
|
||||||
out = null;
|
out = null;
|
||||||
}
|
}
|
||||||
@ -220,7 +208,8 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Item fromNetwork(String destination) {
|
@Nullable
|
||||||
|
private Item fromNetwork(@NonNull String destination) {
|
||||||
|
|
||||||
Item out = null;
|
Item out = null;
|
||||||
|
|
||||||
@ -241,15 +230,8 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
if (body != null) {
|
if (body != null) {
|
||||||
final InputStream inputStream = body.byteStream();
|
final InputStream inputStream = body.byteStream();
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
final String type;
|
|
||||||
final String contentType = response.header(HEADER_CONTENT_TYPE);
|
final String contentType = response.header(HEADER_CONTENT_TYPE);
|
||||||
if (!TextUtils.isEmpty(contentType)
|
out = new Item(null, contentType, inputStream);
|
||||||
&& contentType.startsWith(CONTENT_TYPE_SVG)) {
|
|
||||||
type = CONTENT_TYPE_SVG;
|
|
||||||
} else {
|
|
||||||
type = contentType;
|
|
||||||
}
|
|
||||||
out = new Item(type, inputStream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,94 +239,31 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable handleSvg(InputStream stream) {
|
@Nullable
|
||||||
|
private MediaDecoder mediaDecoderFromFile(@NonNull String fileName) {
|
||||||
|
|
||||||
final Drawable out;
|
MediaDecoder out = null;
|
||||||
|
|
||||||
SVG svg = null;
|
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||||
try {
|
if (mediaDecoder.canDecodeByFileName(fileName)) {
|
||||||
svg = SVG.getFromInputStream(stream);
|
out = mediaDecoder;
|
||||||
} catch (SVGParseException e) {
|
break;
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (svg == null) {
|
|
||||||
out = null;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
final float w = svg.getDocumentWidth();
|
|
||||||
final float h = svg.getDocumentHeight();
|
|
||||||
final float density = resources.getDisplayMetrics().density;
|
|
||||||
|
|
||||||
final int width = (int) (w * density + .5F);
|
|
||||||
final int height = (int) (h * density + .5F);
|
|
||||||
|
|
||||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
|
||||||
final Canvas canvas = new Canvas(bitmap);
|
|
||||||
canvas.scale(density, density);
|
|
||||||
svg.renderToCanvas(canvas);
|
|
||||||
|
|
||||||
out = new BitmapDrawable(resources, bitmap);
|
|
||||||
DrawableUtils.intrinsicBounds(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable handleGif(InputStream stream) {
|
|
||||||
|
|
||||||
Drawable out = null;
|
|
||||||
|
|
||||||
final byte[] bytes = readBytes(stream);
|
|
||||||
if (bytes != null) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
out = new GifDrawable(bytes);
|
|
||||||
DrawableUtils.intrinsicBounds(out);
|
|
||||||
|
|
||||||
// @since 1.1.0
|
|
||||||
if (!autoPlayGif) {
|
|
||||||
((GifDrawable) out).pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable handleSimple(InputStream stream) {
|
@Nullable
|
||||||
|
private MediaDecoder mediaDecoderFromContentType(@Nullable String contentType) {
|
||||||
|
|
||||||
final Drawable out;
|
MediaDecoder out = null;
|
||||||
|
|
||||||
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||||
if (bitmap != null) {
|
if (mediaDecoder.canDecodeByContentType(contentType)) {
|
||||||
out = new BitmapDrawable(resources, bitmap);
|
out = mediaDecoder;
|
||||||
DrawableUtils.intrinsicBounds(out);
|
break;
|
||||||
} else {
|
|
||||||
out = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] readBytes(InputStream stream) {
|
|
||||||
|
|
||||||
byte[] out = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
||||||
final int length = 1024 * 8;
|
|
||||||
final byte[] buffer = new byte[length];
|
|
||||||
int read;
|
|
||||||
while ((read = stream.read(buffer, 0, length)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
out = outputStream.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@ -358,61 +277,92 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
private Drawable errorDrawable;
|
private Drawable errorDrawable;
|
||||||
|
|
||||||
// @since 1.1.0
|
// @since 1.1.0
|
||||||
private boolean autoPlayGif = true;
|
private final List<MediaDecoder> mediaDecoders = new ArrayList<>(3);
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public Builder client(@NonNull OkHttpClient client) {
|
public Builder client(@NonNull OkHttpClient client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplied resources argument will be used to open files from assets directory
|
||||||
|
* and to create default {@link MediaDecoder}\'s which require resources instance
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
public Builder resources(@NonNull Resources resources) {
|
public Builder resources(@NonNull Resources resources) {
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder executorService(ExecutorService executorService) {
|
@NonNull
|
||||||
|
public Builder executorService(@NonNull ExecutorService executorService) {
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder errorDrawable(Drawable errorDrawable) {
|
@NonNull
|
||||||
|
public Builder errorDrawable(@NonNull Drawable errorDrawable) {
|
||||||
this.errorDrawable = errorDrawable;
|
this.errorDrawable = errorDrawable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param autoPlayGif flag indicating if loaded gif should automatically start when displayed
|
|
||||||
* @return self
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder autoPlayGif(boolean autoPlayGif) {
|
public Builder mediaDecoders(@NonNull List<MediaDecoder> mediaDecoders) {
|
||||||
this.autoPlayGif = autoPlayGif;
|
this.mediaDecoders.clear();
|
||||||
|
this.mediaDecoders.addAll(mediaDecoders);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder mediaDecoders(MediaDecoder... mediaDecoders) {
|
||||||
|
this.mediaDecoders.clear();
|
||||||
|
if (mediaDecoders != null
|
||||||
|
&& mediaDecoders.length > 0) {
|
||||||
|
Collections.addAll(this.mediaDecoders, mediaDecoders);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public AsyncDrawableLoader build() {
|
public AsyncDrawableLoader build() {
|
||||||
|
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
client = new OkHttpClient();
|
client = new OkHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resources == null) {
|
if (resources == null) {
|
||||||
resources = Resources.getSystem();
|
resources = Resources.getSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (executorService == null) {
|
if (executorService == null) {
|
||||||
// we will use executor from okHttp
|
// we will use executor from okHttp
|
||||||
executorService = client.dispatcher().executorService();
|
executorService = client.dispatcher().executorService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add default media decoders if not specified
|
||||||
|
if (mediaDecoders.size() == 0) {
|
||||||
|
mediaDecoders.add(SvgMediaDecoder.create(resources));
|
||||||
|
mediaDecoders.add(GifMediaDecoder.create(true));
|
||||||
|
mediaDecoders.add(ImageMediaDecoder.create(resources));
|
||||||
|
}
|
||||||
|
|
||||||
return new AsyncDrawableLoader(this);
|
return new AsyncDrawableLoader(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Item {
|
private static class Item {
|
||||||
final String type;
|
|
||||||
|
final String fileName;
|
||||||
|
final String contentType;
|
||||||
final InputStream inputStream;
|
final InputStream inputStream;
|
||||||
|
|
||||||
Item(String type, InputStream inputStream) {
|
Item(@Nullable String fileName, @Nullable String contentType, @Nullable InputStream inputStream) {
|
||||||
this.type = type;
|
this.fileName = fileName;
|
||||||
|
this.contentType = contentType;
|
||||||
this.inputStream = inputStream;
|
this.inputStream = inputStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class GifMediaDecoder extends MediaDecoder {
|
||||||
|
|
||||||
|
protected static final String CONTENT_TYPE_GIF = "image/gif";
|
||||||
|
protected static final String FILE_EXTENSION_GIF = ".gif";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static GifMediaDecoder create(boolean autoPlayGif) {
|
||||||
|
return new GifMediaDecoder(autoPlayGif);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean autoPlayGif;
|
||||||
|
|
||||||
|
protected GifMediaDecoder(boolean autoPlayGif) {
|
||||||
|
this.autoPlayGif = autoPlayGif;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||||
|
return CONTENT_TYPE_GIF.equals(contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||||
|
return fileName.endsWith(FILE_EXTENSION_GIF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable decode(@NonNull InputStream inputStream) {
|
||||||
|
|
||||||
|
Drawable out = null;
|
||||||
|
|
||||||
|
final byte[] bytes = readBytes(inputStream);
|
||||||
|
if (bytes != null) {
|
||||||
|
try {
|
||||||
|
out = newGifDrawable(bytes);
|
||||||
|
DrawableUtils.intrinsicBounds(out);
|
||||||
|
|
||||||
|
if (!autoPlayGif) {
|
||||||
|
((GifDrawable) out).pause();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected Drawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
|
||||||
|
return new GifDrawable(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected static byte[] readBytes(@NonNull InputStream stream) {
|
||||||
|
|
||||||
|
byte[] out = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
final int length = 1024 * 8;
|
||||||
|
final byte[] buffer = new byte[length];
|
||||||
|
int read;
|
||||||
|
while ((read = stream.read(buffer, 0, length)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
out = outputStream.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases.
|
||||||
|
* Here we just assume that supplied InputStream is of image type and try to decode it.
|
||||||
|
*
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class ImageMediaDecoder extends MediaDecoder {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static ImageMediaDecoder create(@NonNull Resources resources) {
|
||||||
|
return new ImageMediaDecoder(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Resources resources;
|
||||||
|
|
||||||
|
ImageMediaDecoder(Resources resources) {
|
||||||
|
this.resources = resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable decode(@NonNull InputStream inputStream) {
|
||||||
|
|
||||||
|
final Drawable out;
|
||||||
|
|
||||||
|
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||||
|
if (bitmap != null) {
|
||||||
|
out = new BitmapDrawable(resources, bitmap);
|
||||||
|
DrawableUtils.intrinsicBounds(out);
|
||||||
|
} else {
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public abstract class MediaDecoder {
|
||||||
|
|
||||||
|
public abstract boolean canDecodeByContentType(@Nullable String contentType);
|
||||||
|
|
||||||
|
public abstract boolean canDecodeByFileName(@NonNull String fileName);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract Drawable decode(@NonNull InputStream inputStream);
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
import com.caverock.androidsvg.SVGParseException;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class SvgMediaDecoder extends MediaDecoder {
|
||||||
|
|
||||||
|
private static final String CONTENT_TYPE_SVG = "image/svg+xml";
|
||||||
|
private static final String FILE_EXTENSION_SVG = ".svg";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static SvgMediaDecoder create(@NonNull Resources resources) {
|
||||||
|
return new SvgMediaDecoder(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Resources resources;
|
||||||
|
|
||||||
|
SvgMediaDecoder(Resources resources) {
|
||||||
|
this.resources = resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByContentType(@Nullable String contentType) {
|
||||||
|
return contentType != null && contentType.startsWith(CONTENT_TYPE_SVG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeByFileName(@NonNull String fileName) {
|
||||||
|
return fileName.endsWith(FILE_EXTENSION_SVG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable decode(@NonNull InputStream inputStream) {
|
||||||
|
|
||||||
|
final Drawable out;
|
||||||
|
|
||||||
|
SVG svg = null;
|
||||||
|
try {
|
||||||
|
svg = SVG.getFromInputStream(inputStream);
|
||||||
|
} catch (SVGParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svg == null) {
|
||||||
|
out = null;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final float w = svg.getDocumentWidth();
|
||||||
|
final float h = svg.getDocumentHeight();
|
||||||
|
final float density = resources.getDisplayMetrics().density;
|
||||||
|
|
||||||
|
final int width = (int) (w * density + .5F);
|
||||||
|
final int height = (int) (h * density + .5F);
|
||||||
|
|
||||||
|
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
||||||
|
final Canvas canvas = new Canvas(bitmap);
|
||||||
|
canvas.scale(density, density);
|
||||||
|
svg.renderToCanvas(canvas);
|
||||||
|
|
||||||
|
out = new BitmapDrawable(resources, bitmap);
|
||||||
|
DrawableUtils.intrinsicBounds(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user