diff --git a/CHANGELOG.md b/CHANGELOG.md
index 005a79c8..1713a4ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+# Snapshot
+
+#### Added
+* `image` - `DefaultDownScalingMediaDecoder` which scales displayed images down ([#329])
+
+[#329]: https://github.com/noties/Markwon/issues/329
+
+
 # 4.6.1
 
 #### Changed
diff --git a/app-sample/samples.json b/app-sample/samples.json
index 23fcb84b..7be52b51 100644
--- a/app-sample/samples.json
+++ b/app-sample/samples.json
@@ -1,4 +1,16 @@
 [
+  {
+    "javaClassName": "io.noties.markwon.app.samples.image.HugeImageSample",
+    "id": "20210118165230",
+    "title": "Huge image downscaling",
+    "description": "Downscale displayed images with `BitmapOptions` 2 step rendering (measure, downscale), use `DefaultDownScalingMediaDecoder`",
+    "artifacts": [
+      "IMAGE"
+    ],
+    "tags": [
+      "image"
+    ]
+  },
   {
     "javaClassName": "io.noties.markwon.app.samples.html.HtmlCssStyleParserSample",
     "id": "20210118155530",
diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/image/HugeImageSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/image/HugeImageSample.java
new file mode 100644
index 00000000..8e158f83
--- /dev/null
+++ b/app-sample/src/main/java/io/noties/markwon/app/samples/image/HugeImageSample.java
@@ -0,0 +1,69 @@
+package io.noties.markwon.app.samples.image;
+
+import android.view.ViewTreeObserver;
+
+import io.noties.markwon.Markwon;
+import io.noties.markwon.app.sample.Tags;
+import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
+import io.noties.markwon.image.DefaultDownScalingMediaDecoder;
+import io.noties.markwon.image.ImagesPlugin;
+import io.noties.markwon.sample.annotations.MarkwonArtifact;
+import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
+
+@MarkwonSampleInfo(
+  id = "20210118165230",
+  title = "Huge image downscaling",
+  description = "Downscale displayed images with `BitmapOptions` 2 step rendering " +
+    "(measure, downscale), use `DefaultDownScalingMediaDecoder`",
+  artifacts = MarkwonArtifact.IMAGE,
+  tags = Tags.image
+)
+public class HugeImageSample extends MarkwonTextViewSample {
+  @Override
+  public void render() {
+
+    // NB! this is based on the width of the widget. In case you have big vertical
+    //  images (with big vertical dimension, use some reasonable value or fallback to real OpenGL
+    //  maximum, see: https://stackoverflow.com/questions/15313807/android-maximum-allowed-width-height-of-bitmap
+
+    final int width = textView.getWidth();
+    if (width > 0) {
+      renderWithMaxWidth(width);
+      return;
+    }
+
+    textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+      @Override
+      public boolean onPreDraw() {
+        final int w = textView.getWidth();
+        if (w > 0) {
+          renderWithMaxWidth(w);
+
+          final ViewTreeObserver observer = textView.getViewTreeObserver();
+          if (observer.isAlive()) {
+            observer.removeOnPreDrawListener(this);
+          }
+        }
+        return true;
+      }
+    });
+  }
+
+  private void renderWithMaxWidth(int maxWidth) {
+
+    final String md = "" +
+      "# Huge image\n\n" +
+      "\n\n" +
+      "hey!";
+
+    final Markwon markwon = Markwon.builder(context)
+      .usePlugin(ImagesPlugin.create(plugin -> {
+        plugin
+          .defaultMediaDecoder(DefaultDownScalingMediaDecoder.create(maxWidth, 0));
+      }))
+      .build();
+
+    markwon.setMarkdown(textView, md);
+  }
+
+}
diff --git a/gradle.properties b/gradle.properties
index ea77f9f8..78c2b1f6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ android.enableJetifier=true
 android.enableBuildCache=true
 android.buildCacheDir=build/pre-dex-cache
 
-VERSION_NAME=4.6.1
+VERSION_NAME=4.6.2-SNAPSHOT
 
 GROUP=io.noties.markwon
 POM_DESCRIPTION=Markwon markdown for Android
diff --git a/markwon-image/src/main/java/io/noties/markwon/image/DefaultDownScalingMediaDecoder.java b/markwon-image/src/main/java/io/noties/markwon/image/DefaultDownScalingMediaDecoder.java
new file mode 100644
index 00000000..2d7d49de
--- /dev/null
+++ b/markwon-image/src/main/java/io/noties/markwon/image/DefaultDownScalingMediaDecoder.java
@@ -0,0 +1,173 @@
+package io.noties.markwon.image;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A {@link MediaDecoder} that additionally process media resource to optionally
+ * scale it down to fit specified maximum values. Should be used to ensure that no exception is raised
+ * whilst rendering ({@code Canvas: trying to draw too large(Xbytes) bitmap}) or {@code OutOfMemoryException} is thrown.
+ *
+ * NB this media decoder will create a temporary file for each incoming media resource,
+ * which can have a performance penalty (IO)
+ *
+ * @since $SNAPSHOT;
+ */
+public class DefaultDownScalingMediaDecoder extends MediaDecoder {
+
+  /**
+   * Values {@code <= 0} are ignored, a dimension is considered to be not restrained any limit in such case
+   */
+  @NonNull
+  public static DefaultDownScalingMediaDecoder create(int maxWidth, int maxHeight) {
+    return create(Resources.getSystem(), maxWidth, maxHeight);
+  }
+
+  @NonNull
+  public static DefaultDownScalingMediaDecoder create(
+    @NonNull Resources resources,
+    int maxWidth,
+    int maxHeight
+  ) {
+    return new DefaultDownScalingMediaDecoder(resources, maxWidth, maxHeight);
+  }
+
+  private final Resources resources;
+  private final int maxWidth;
+  private final int maxHeight;
+
+  private DefaultDownScalingMediaDecoder(@NonNull Resources resources, int maxWidth, int maxHeight) {
+    this.resources = resources;
+    this.maxWidth = maxWidth;
+    this.maxHeight = maxHeight;
+  }
+
+  // https://android.jlelse.eu/loading-large-bitmaps-efficiently-in-android-66826cd4ad53
+  @NonNull
+  @Override
+  public Drawable decode(@Nullable String contentType, @NonNull InputStream inputStream) {
+
+    final File file = writeToTempFile(inputStream);
+    try {
+
+      final BitmapFactory.Options options = new BitmapFactory.Options();
+      options.inJustDecodeBounds = true;
+
+      BitmapFactory
+        .decodeStream(readFile(file), null, options);
+
+      options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+      options.inJustDecodeBounds = false;
+
+      final Bitmap bitmap = BitmapFactory
+        .decodeStream(readFile(file), null, options);
+
+      return new BitmapDrawable(resources, bitmap);
+    } finally {
+      // we no longer need the temporary file
+      //noinspection ResultOfMethodCallIgnored
+      file.delete();
+    }
+  }
+
+  @NonNull
+  private static File writeToTempFile(@NonNull InputStream inputStream) {
+    final File file;
+    try {
+      file = File.createTempFile("markwon", null);
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    }
+
+    final OutputStream outputStream;
+    try {
+      outputStream = new BufferedOutputStream(new FileOutputStream(file, false));
+    } catch (FileNotFoundException e) {
+      throw new IllegalStateException(e);
+    }
+
+    final byte[] buffer = new byte[1024 * 8];
+    int length;
+    try {
+      while ((length = inputStream.read(buffer)) > 0) {
+        outputStream.write(buffer, 0, length);
+      }
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    } finally {
+      try {
+        outputStream.close();
+      } catch (IOException e) {
+        // ignored
+      }
+    }
+
+    return file;
+  }
+
+  @NonNull
+  private static InputStream readFile(@NonNull File file) {
+    try {
+      return new BufferedInputStream(new FileInputStream(file));
+    } catch (FileNotFoundException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  // see: https://developer.android.com/topic/performance/graphics/load-bitmap.html#load-bitmap
+  private static int calculateInSampleSize(@NonNull BitmapFactory.Options options, int maxWidth, int maxHeight) {
+    final int w = options.outWidth;
+    final int h = options.outHeight;
+
+    final boolean hasMaxWidth = maxWidth > 0;
+    final boolean hasMaxHeight = maxHeight > 0;
+
+    final int inSampleSize;
+    if (hasMaxWidth && hasMaxHeight) {
+      // minimum of both
+      inSampleSize = Math.min(calculateInSampleSize(w, maxWidth), calculateInSampleSize(h, maxHeight));
+    } else if (hasMaxWidth) {
+      inSampleSize = calculateInSampleSize(w, maxWidth);
+    } else if (hasMaxHeight) {
+      inSampleSize = calculateInSampleSize(h, maxHeight);
+    } else {
+      // else no sampling, as we have no dimensions to base our calculations on
+      inSampleSize = 1;
+    }
+
+    return inSampleSize;
+  }
+
+  private static int calculateInSampleSize(int actual, int max) {
+    int inSampleSize = 1;
+    final int half = actual / 2;
+    while ((half / inSampleSize) > max) {
+      inSampleSize *= 2;
+    }
+    return inSampleSize;
+  }
+
+  @NonNull
+  @Override
+  public Collection supportedTypes() {
+    return Collections.emptySet();
+  }
+}
diff --git a/markwon-image/src/main/java/io/noties/markwon/image/DefaultMediaDecoder.java b/markwon-image/src/main/java/io/noties/markwon/image/DefaultMediaDecoder.java
index 0fc18824..e60c225e 100644
--- a/markwon-image/src/main/java/io/noties/markwon/image/DefaultMediaDecoder.java
+++ b/markwon-image/src/main/java/io/noties/markwon/image/DefaultMediaDecoder.java
@@ -17,6 +17,10 @@ import java.util.Collections;
  * 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.
  *
+ * NB if you are dealing with big images that require down scaling see {@link DefaultDownScalingMediaDecoder}
+ * which additionally down scales displayed images.
+ *
+ * @see DefaultDownScalingMediaDecoder
  * @since 1.1.0
  */
 public class DefaultMediaDecoder extends MediaDecoder {