diff --git a/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt
index 0e40fee4f171f3abfb4ba3ca72ff025e0d848d05..fe811dc42209e872644c838cbcca238d79b56684 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt
@@ -20,6 +20,8 @@ package org.briarproject.briar.desktop.attachment.media
 
 import mu.KotlinLogging
 import org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE
+import java.awt.geom.AffineTransform
+import java.awt.image.AffineTransformOp
 import java.awt.image.BufferedImage
 import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
@@ -29,19 +31,39 @@ import javax.imageio.IIOImage
 import javax.imageio.ImageIO
 import javax.imageio.ImageWriteParam
 import javax.inject.Inject
+import kotlin.math.max
 
 class ImageCompressorImpl @Inject internal constructor() : ImageCompressor {
 
     companion object {
         val LOG = KotlinLogging.logger {}
+
+        const val MAX_ATTACHMENT_DIMENSION = 1000
     }
 
     override fun compressImage(image: BufferedImage): InputStream {
         val out = ByteArrayOutputStream()
+
+        // First make sure we're dealing with an image without alpha channel. Alpha channels are not supported by JPEG
+        // compression later on. If the image contains an alpha channel, we draw it onto an image without alpha channel.
         val withoutAlpha = if (!image.colorModel.hasAlpha()) image else {
             val replacement = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
             replacement.apply { createGraphics().drawImage(image, 0, 0, null) }
         }
+
+        // Now determine the factor by which we scale down the image in order to end up with an image
+        // with both sides smaller than [MAX_ATTACHMENT_DIMENSION].
+        val maxSize = max(withoutAlpha.width, withoutAlpha.height)
+        var factor = 1
+        while (maxSize / factor > MAX_ATTACHMENT_DIMENSION) {
+            factor *= 2
+        }
+
+        // Now, if we determined a factor greater 1 reduce image dimensions
+        val scaled = if (factor != 1) scaleDown(withoutAlpha, factor) else withoutAlpha
+
+        // After that, compress image. Try with maximum quality and reduce until we can compress below
+        // a size of [MAX_IMAGE_SIZE]. We try quality levels 100, 90, ..., 20, 10.
         for (quality in 100 downTo 1 step 10) {
             val jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next()
             jpgWriter.output = ImageIO.createImageOutputStream(out)
@@ -50,7 +72,7 @@ class ImageCompressorImpl @Inject internal constructor() : ImageCompressor {
             jpgWriteParam.compressionMode = ImageWriteParam.MODE_EXPLICIT
             jpgWriteParam.compressionQuality = quality / 100f
 
-            val outputImage = IIOImage(withoutAlpha, null, null)
+            val outputImage = IIOImage(scaled, null, null)
             jpgWriter.write(null, outputImage, jpgWriteParam)
 
             jpgWriter.dispose()
@@ -62,4 +84,22 @@ class ImageCompressorImpl @Inject internal constructor() : ImageCompressor {
         }
         throw IOException()
     }
+
+    private fun scaleDown(image: BufferedImage, factor: Int): BufferedImage {
+        // Calculate new images dimensions
+        val w = image.width / factor
+        val h = image.height / factor
+        // Determine vertical and horizontal scale factors. We accept some minimal distortion here as it is quite
+        // possible that the integer division above led to different effective scale factors. Since we need integral
+        // dimensions there's not much we could do about that.
+        val sx = w / image.width.toDouble()
+        val sy = h / image.height.toDouble()
+        // Create new image of same type and scale down
+        var resized = BufferedImage(w, h, image.type)
+        val at = AffineTransform().also {
+            it.scale(sx, sy)
+        }
+        val scale = AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR)
+        return scale.filter(image, resized)
+    }
 }
diff --git a/src/test/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorTest.kt b/src/test/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorTest.kt
index 62647bc9c278313364a8135741bf5110c4d69306..f88fee4fa4394a775be042c8f2470a5211e7c29b 100644
--- a/src/test/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorTest.kt
+++ b/src/test/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorTest.kt
@@ -27,7 +27,7 @@ class ImageCompressorTest {
     private val compressor = ImageCompressorImpl()
 
     @Test
-    fun `can compress voronoi image`() {
+    fun `can compress voronoi diagram`() {
         // load image
         val input = Thread.currentThread().contextClassLoader.getResourceAsStream("images/voronoi1.png")
         val image = input.use {
@@ -64,5 +64,4 @@ class ImageCompressorTest {
         }
         println("image size: ${reloaded.width}x${reloaded.height}")
     }
-
 }