diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py
index a9bdfeba9bc90f0f4afffc1951b549ec9166cf26..3f82e34ebd1ac42f5cbe956be4c3487e7e55ab6f 100644
--- a/httpupload/httpupload.py
+++ b/httpupload/httpupload.py
@@ -4,20 +4,11 @@
 from gi.repository import GObject, Gtk
 import os
 import time
-import base64
-import tempfile
 from urllib.request import Request, urlopen
-from urllib.parse import quote as urlquote
 import mimetypes        # better use the magic packet, but that's not a standard lib
 import gtkgui_helpers
+import logging
 from queue import Queue
-try:
-    from PIL import Image
-    pil_available = True
-except:
-    pil_available = False
-from io import BytesIO
-import base64
 import binascii
 
 from common import gajim
@@ -25,10 +16,11 @@ from common import ged
 import chat_control
 from plugins import GajimPlugin
 from plugins.helpers import log_calls
-import logging
 from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog
 import nbxmpp
 
+from .thumbnail import thumbnail
+
 log = logging.getLogger('gajim.plugin_system.httpupload')
 
 if os.name != 'nt':
@@ -52,8 +44,6 @@ TAGSIZE = 16
 jid_to_servers = {}
 iq_ids_to_callbacks = {}
 last_info_query = {}
-max_thumbnail_size = 2048
-max_thumbnail_dimension = 160
 
 
 class HttpuploadPlugin(GajimPlugin):
@@ -125,10 +115,11 @@ class HttpuploadPlugin(GajimPlugin):
             #pass
 
         # query info at most every 60 seconds in case something goes wrong
-        if (not chat_control.account in last_info_query or \
-            last_info_query[chat_control.account] + 60 < time.time()) and \
-            not gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \
-            gajim.account_is_connected(chat_control.account):
+        if ((not chat_control.account in last_info_query or
+             last_info_query[chat_control.account] + 60 < time.time())
+            and not gajim.get_jid_from_account(chat_control.account) in jid_to_servers
+            and gajim.account_is_connected(chat_control.account)
+        ):
             log.info("Account %s: Using dicovery to find jid of httpupload component" % chat_control.account)
             id_ = gajim.get_an_id()
             iq = nbxmpp.Iq(
@@ -289,11 +280,11 @@ class Base(object):
                 progress_window.close_dialog()
                 error = stanza.getTag("error")
                 if error and error.getTag("text"):
-                    ErrorDialog(_('Could not request upload slot'), 
+                    ErrorDialog(_('Could not request upload slot'),
                                 _('Got unexpected response from server: %s') % str(error.getTagData("text")),
                                 transient_for=self.chat_control.parent_win.window)
                 else:
-                    ErrorDialog(_('Could not request upload slot'), 
+                    ErrorDialog(_('Could not request upload slot'),
                                 _('Got unexpected response from server (protocol mismatch??)'),
                                 transient_for=self.chat_control.parent_win.window)
                 return
@@ -313,7 +304,7 @@ class Base(object):
             except:
                 log.error("Could not open file")
                 progress_window.close_dialog()
-                ErrorDialog(_('Could not open file'), 
+                ErrorDialog(_('Could not open file'),
                             _('Exception raised while opening file (see error log for more information)'),
                             transient_for=self.chat_control.parent_win.window)
                 raise       # fill error log with useful information
@@ -323,7 +314,7 @@ class Base(object):
             if not put or not get:
                 log.error("got unexpected stanza: " + str(stanza))
                 progress_window.close_dialog()
-                ErrorDialog(_('Could not request upload slot'), 
+                ErrorDialog(_('Could not request upload slot'),
                             _('Got unexpected response from server (protocol mismatch??)'),
                             transient_for=self.chat_control.parent_win.window)
                 return
@@ -335,69 +326,17 @@ class Base(object):
                     log.info("Upload completed successfully")
                     xhtml = None
                     is_image = mime_type.split('/', 1)[0] == 'image'
-                    if (not isinstance(self.chat_control, chat_control.ChatControl) or not self.chat_control.gpg_is_active) and \
-                        self.dialog_type == 'image' and is_image and not self.encrypted_upload:
-
+                    if ((not isinstance(self.chat_control, chat_control.ChatControl)
+                         or not self.chat_control.gpg_is_active)
+                        and self.dialog_type == 'image'
+                        and is_image
+                        and not self.encrypted_upload
+                    ):
                         progress_messages.put(_('Calculating (possible) image thumbnail...'))
-                        thumb = None
-                        quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
-                        with open(path_to_file, 'rb') as content_file:
-                            thumb = urlquote(base64.standard_b64encode(content_file.read()), '')
-                        if thumb and len(thumb) < max_thumbnail_size:
-                            quality = 100
-                            log.info("Image small enough (%d bytes), not resampling" % len(thumb))
-                        elif pil_available:
-                            log.info("PIL available, using it for image downsampling")
-                            try:
-                                for quality in quality_steps:
-                                    thumb = Image.open(path_to_file)
-                                    thumb.thumbnail((max_thumbnail_dimension, max_thumbnail_dimension), Image.ANTIALIAS)
-                                    output = BytesIO()
-                                    thumb.save(output, format='JPEG', quality=quality, optimize=True)
-                                    thumb = output.getvalue()
-                                    output.close()
-                                    thumb = urlquote(base64.standard_b64encode(thumb), '')
-                                    log.debug("pil thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
-                                    if len(thumb) < max_thumbnail_size:
-                                        break
-                            except:
-                                thumb = None
-                        else:
-                            thumb = None
-                        if not thumb:
-                            log.info("PIL not available, using GTK for image downsampling")
-                            temp_file = None
-                            try:
-                                with open(path_to_file, 'rb') as content_file:
-                                    thumb = content_file.read()
-                                loader = Gtk.gdk.PixbufLoader()
-                                loader.write(thumb)
-                                loader.close()
-                                pixbuf = loader.get_pixbuf()
-                                scaled_pb = self.get_pixbuf_of_size(pixbuf, max_thumbnail_dimension)
-                                handle, temp_file = tempfile.mkstemp(suffix='.jpeg', prefix='gajim_httpupload_scaled_tmp', dir=gajim.TMP)
-                                log.debug("Saving temporary jpeg image to '%s'..." % temp_file)
-                                os.close(handle)
-                                for quality in quality_steps:
-                                    scaled_pb.save(temp_file, "jpeg", {"quality": str(quality)})
-                                    with open(temp_file, 'rb') as content_file:
-                                        thumb = content_file.read()
-                                    thumb = urlquote(base64.standard_b64encode(thumb), '')
-                                    log.debug("gtk thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
-                                    if len(thumb) < max_thumbnail_size:
-                                        break
-                            except:
-                                thumb = None
-                            finally:
-                                if temp_file:
-                                    os.unlink(temp_file)
+                        thumb = thumbnail(path_to_file)
                         if thumb:
-                            if len(thumb) > max_thumbnail_size:
-                                log.info("Couldn't compress image enough, not sending any thumbnail")
-                            else:
-                                log.info("Using thumbnail jpeg quality %d (image size: %d bytes)" % (quality, len(thumb)))
-                                xhtml = '<body><br/><a href="%s"> <img alt="%s" src="data:image/png;base64,%s"/> </a></body>' % \
-                                    (get.getData(), get.getData(), thumb)
+                            xhtml = '<body><br/><a href="%s"><img alt="%s" src="data:image/jpeg;base64,%s"/></a></body>' % \
+                                (get.getData(), get.getData(), thumb)
                     progress_window.close_dialog()
                     id_ = gajim.get_an_id()
                     def add_oob_tag():
@@ -414,7 +353,7 @@ class Base(object):
                     ErrorDialog(_('Could not upload file'),
                                 _('Got unexpected http response code from server: ') + str(response_code),
                                 transient_for=self.chat_control.parent_win.window)
-            
+
             def uploader():
                 progress_messages.put(_('Uploading file via HTTP...'))
                 try:
@@ -495,26 +434,6 @@ class Base(object):
         self.dialog_type = 'image'
         self.dlg = ImageChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None)
 
-    def get_pixbuf_of_size(self, pixbuf, size):
-        # Creates a pixbuf that fits in the specified square of sizexsize
-        # while preserving the aspect ratio
-        # Returns scaled_pixbuf
-        image_width = pixbuf.get_width()
-        image_height = pixbuf.get_height()
-
-        if image_width > image_height:
-            if image_width > size:
-                image_height = int(size / float(image_width) * image_height)
-                image_width = int(size)
-        else:
-            if image_height > size:
-                image_width = int(size / float(image_height) * image_width)
-                image_height = int(size)
-
-        crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
-            Gtk.gdk.INTERP_BILINEAR)
-        return crop_pixbuf
-
 
 class StreamFileWithProgress:
     def __init__(self, path, mode, callback=None,
diff --git a/httpupload/thumbnail.py b/httpupload/thumbnail.py
new file mode 100644
index 0000000000000000000000000000000000000000..b55660d5e6e19c5b9ac251dd8f5f0ac4ab13c0e5
--- /dev/null
+++ b/httpupload/thumbnail.py
@@ -0,0 +1,90 @@
+from gi.repository import GdkPixbuf
+import base64
+from io import BytesIO
+import os
+import sys
+import logging
+from urllib.parse import quote as urlquote
+try:
+    from PIL import Image
+    pil_available = True
+except:
+    pil_available = False
+
+log = logging.getLogger('gajim.plugin_system.httpupload.thumbnail')
+
+def scale_down_to(pixbuf, size):
+    # Creates a pixbuf that fits in the specified square of sizexsize
+    # while preserving the aspect ratio
+    # Returns scaled_pixbuf
+    image_width = pixbuf.get_width()
+    image_height = pixbuf.get_height()
+
+    if image_width > image_height:
+        if image_width > size:
+            image_height = int(size / float(image_width) * image_height)
+            image_width = int(size)
+    else:
+        if image_height > size:
+            image_width = int(size / float(image_height) * image_width)
+            image_height = int(size)
+
+    crop_pixbuf = pixbuf.scale_simple(image_width, image_height, GdkPixbuf.InterpType.BILINEAR)
+    return crop_pixbuf
+
+
+max_thumbnail_size = 2048
+max_thumbnail_dimension = 160
+base64_size_factor = 4/3
+def thumbnail(path_to_file):
+    """
+    Generates a JPEG thumbnail and base64-encodes, ensuring that the encoded
+    size is less than max_thumbnail_size bytes. If this is not possible, returns
+    None.
+    """
+    thumb = None
+    quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
+    # If the whole file is small enough, we'll just use that as a thumbnail
+    # without downsampling.
+    if os.path.getsize(path_to_file) * base64_size_factor < max_thumbnail_size:
+        with open(path_to_file, 'rb') as content_file:
+            thumb = urlquote(base64.standard_b64encode(content_file.read()), '')
+            log.info("Image small enough (%d bytes), not resampling" % len(thumb))
+            return thumb
+    elif pil_available:
+        log.info("PIL available, using it for image downsampling")
+        try:
+            for quality in quality_steps:
+                thumb = Image.open(path_to_file)
+                thumb.thumbnail((max_thumbnail_dimension, max_thumbnail_dimension), Image.ANTIALIAS)
+                output = BytesIO()
+                thumb.save(output, format='JPEG', quality=quality, optimize=True)
+                thumb = output.getvalue()
+                output.close()
+                thumb = urlquote(base64.standard_b64encode(thumb), '')
+                log.debug("pil thumbnail jpeg quality %d produces an image of size %d...", quality, len(thumb))
+                if len(thumb) < max_thumbnail_size:
+                    log.debug("Size is acceptable.")
+                    return thumb
+        except:
+            log.info("Exception occurred during PIL downsampling", exc_info=sys.exc_info())
+            thumb = None
+    # If we haven't returned by now we couldn't use PIL for one reason or
+    # another, so let's pass on to GdkPixbuf
+    log.info("using GdkPixBuf for image downsampling")
+    temp_file = None
+    try:
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(path_to_file)
+        scaled_pb = scale_down_to(pixbuf, max_thumbnail_dimension)
+        for quality in quality_steps:
+            success, thumb_raw = scaled_pb.save_to_bufferv("jpeg", ["quality"], [str(quality)])
+            log.debug("gdkpixbuf thumbnail jpeg quality %d produces an image of size %d...",
+                      quality,
+                      len(thumb_raw) * base64_size_factor)
+            if len(thumb_raw) * base64_size_factor < max_thumbnail_size:
+                log.debug("Size is acceptable.")
+                return urlquote(base64.standard_b64encode(thumb_raw))
+    except:
+        log.info("Exception occurred during GdkPixbuf downsampling, not providing thumbnail", exc_info=sys.exc_info())
+        return None
+    log.info("No acceptably small thumbnail was generated.")