diff --git a/reviewboard/attachments/mimetypes.py b/reviewboard/attachments/mimetypes.py
index 48ea1e10848e6848695582cfd61bb4958218629b..9db980bc6f0079c94548a806c6d86c6e4beb7765 100644
--- a/reviewboard/attachments/mimetypes.py
+++ b/reviewboard/attachments/mimetypes.py
@@ -6,7 +6,7 @@ import subprocess
 
 from django.contrib.staticfiles.storage import staticfiles_storage
 from django.contrib.staticfiles.templatetags.staticfiles import static
-from django.utils.html import escape
+from django.utils.html import format_html
 from django.utils.encoding import smart_str, force_unicode
 from django.utils.safestring import mark_safe
 from djblets.cache.backend import cache_memoize
@@ -305,13 +305,14 @@ class ImageMimetype(MimetypeHandler):
 
     def get_thumbnail(self):
         """Return a thumbnail of the image."""
-        return mark_safe(
+        return format_html(
             '<div class="file-thumbnail">'
-            ' <img src="%s" data-at2x="%s" alt="%s" />'
-            '</div>'
-            % (thumbnail(self.attachment.file, (300, None)),
-               thumbnail(self.attachment.file, (600, None)),
-               escape(self.attachment.caption)))
+            ' <img src="{src_1x}" srcset="{src_1x} 1x, {src_2x} 2x"'
+            ' alt="{caption}" width="300" />'
+            '</div>',
+            src_1x=thumbnail(self.attachment.file, (300, None)),
+            src_2x=thumbnail(self.attachment.file, (600, None)),
+            caption=self.attachment.caption)
 
 
 class TextMimetype(MimetypeHandler):
diff --git a/reviewboard/reviews/models/screenshot.py b/reviewboard/reviews/models/screenshot.py
index b5af07667f68dc12ad4726c87b6b2d360777f9a1..229affcb0c76e17a1caadf92cba58d3b60de1233 100644
--- a/reviewboard/reviews/models/screenshot.py
+++ b/reviewboard/reviews/models/screenshot.py
@@ -6,7 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.db import models
 from django.utils import timezone
 from django.utils.encoding import python_2_unicode_compatible
-from django.utils.html import escape
+from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy as _
 from djblets.util.templatetags.djblets_images import thumbnail
@@ -48,9 +48,12 @@ class Screenshot(models.Model):
     def thumb(self):
         """Creates and returns HTML for this screenshot's thumbnail."""
         url = self.get_thumbnail_url()
-        return mark_safe('<img src="%s" data-at2x="%s" alt="%s" />' %
-                         (url, thumbnail(self.image, '800x200'),
-                          escape(self.caption)))
+        return format_html(
+            '<img src="{src_1x}" srcset="{src_1x} 1x, {src_2x} 2x"'
+            ' alt="{caption}" width="400" />',
+            src_1x=url,
+            src_2x=thumbnail(self.image, '800x200'),
+            caption=self.caption)
     thumb.allow_tags = True
 
     def __str__(self):
diff --git a/reviewboard/static/lib/js/retina.js b/reviewboard/static/lib/js/retina.js
deleted file mode 100644
index 8aaed878d73ecd82fe9b11b3d98fe959a9fd316c..0000000000000000000000000000000000000000
--- a/reviewboard/static/lib/js/retina.js
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * retina.js - JavaScript helper for rendering high-resolution image variants.
- *
- * https://github.com/imulus/retinajs
- *
- * Copyright (C) 2012 Imulus
- * Copyright (C) 2012 Ben Atkin
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-(function() {
-
-  var root = (typeof exports == 'undefined' ? window : exports);
-
-  var config = {
-    // Ensure Content-Type is an image before trying to load @2x image
-    // https://github.com/imulus/retinajs/pull/45)
-    check_mime_type: true
-  };
-
-
-
-  root.Retina = Retina;
-
-  function Retina() {}
-
-  Retina.configure = function(options) {
-    if (options == null) options = {};
-    for (var prop in options) config[prop] = options[prop];
-  };
-
-  Retina.init = function(context) {
-    if (context == null) context = root;
-
-    var existing_onload = context.onload || new Function;
-
-    context.onload = function() {
-      var images = document.getElementsByTagName("img"), retinaImages = [], i, image;
-      for (i = 0; i < images.length; i++) {
-        image = images[i];
-        retinaImages.push(new RetinaImage(image));
-      }
-      existing_onload();
-    }
-  };
-
-  Retina.isRetina = function(){
-    var mediaQuery = "(-webkit-min-device-pixel-ratio: 1.5),\
-                      (min--moz-device-pixel-ratio: 1.5),\
-                      (-o-min-device-pixel-ratio: 3/2),\
-                      (min-resolution: 1.5dppx)";
-
-    if (root.devicePixelRatio > 1)
-      return true;
-
-    if (root.matchMedia && root.matchMedia(mediaQuery).matches)
-      return true;
-
-    return false;
-  };
-
-
-  root.RetinaImagePath = RetinaImagePath;
-
-  function RetinaImagePath(path, at_2x_path) {
-    this.path = path;
-    if (typeof at_2x_path !== "undefined" && at_2x_path !== null) {
-      this.at_2x_path = at_2x_path;
-      this.perform_check = false;
-    } else {
-      this.at_2x_path = path.replace(/\.\w+$/, function(match) { return "@2x" + match; });
-      this.perform_check = true;
-    }
-  }
-
-  RetinaImagePath.confirmed_paths = [];
-
-  RetinaImagePath.prototype.is_external = function() {
-    return !!(this.path.match(/^https?\:/i) && !this.path.match('//' + document.domain) )
-  }
-
-  RetinaImagePath.prototype.check_2x_variant = function(callback) {
-    var http, that = this;
-    if (this.perform_check && this.is_external()) {
-      return callback(false);
-    } else if (!this.perform_check && typeof this.at_2x_path !== "undefined" && this.at_2x_path !== null) {
-      return callback(true);
-    } else if (this.at_2x_path in RetinaImagePath.confirmed_paths) {
-      return callback(true);
-    } else {
-      http = new XMLHttpRequest;
-      http.open('HEAD', this.at_2x_path);
-      http.onreadystatechange = function() {
-        if (http.readyState != 4) {
-          return callback(false);
-        }
-
-        if (http.status >= 200 && http.status <= 399) {
-          if (config.check_mime_type) {
-            var type = http.getResponseHeader('Content-Type');
-            if (type == null || !type.match(/^image/i)) {
-              return callback(false);
-            }
-          }
-
-          RetinaImagePath.confirmed_paths.push(that.at_2x_path);
-          return callback(true);
-        } else {
-          return callback(false);
-        }
-      }
-      http.send();
-    }
-  }
-
-
-
-  function RetinaImage(el) {
-    this.el = el;
-    this.path = new RetinaImagePath(this.el.getAttribute('src'), this.el.getAttribute('data-at2x'));
-    var that = this;
-    this.path.check_2x_variant(function(hasVariant) {
-      if (hasVariant) that.swap();
-    });
-  }
-
-  root.RetinaImage = RetinaImage;
-
-  RetinaImage.prototype.swap = function(path) {
-    if (typeof path == 'undefined') path = this.path.at_2x_path;
-
-    var that = this;
-    function load() {
-      if (! that.el.complete) {
-        setTimeout(load, 5);
-      } else {
-        that.el.setAttribute('width', that.el.naturalWidth || that.el.offsetWidth);
-        that.el.setAttribute('height', that.el.naturalHeight || that.el.offsetHeight);
-        that.el.setAttribute('src', path);
-      }
-    }
-    load();
-  }
-
-
-
-
-  if (Retina.isRetina()) {
-    Retina.init(root);
-  }
-
-})();
-
diff --git a/reviewboard/static/rb/css/assets/icons.less b/reviewboard/static/rb/css/assets/icons.less
index 8da13ccdcb6f8d51db8d5208a7487cd0f5a598ee..b1157361af9ba85ede73aaff30de9d563ec2321c 100644
--- a/reviewboard/static/rb/css/assets/icons.less
+++ b/reviewboard/static/rb/css/assets/icons.less
@@ -1,4 +1,4 @@
-@import (reference) "../mixins/retina.less";
+@import (reference) "djblets/css/mixins/retina.less";
 
 
 @img_base: '../../images';
@@ -11,8 +11,7 @@
   text-indent: -99999px;
   vertical-align: middle;
 
-  background-image: url("@{img_base}/icons.png");
-  .at2x('@{img_base}/icons.png', 155px, 125px);
+  .retina('@{img_base}/icons.png', 155px 125px, @has-svg: true);
 
   // For IE7
   zoom: 1;
diff --git a/reviewboard/static/rb/css/mixins/retina.less b/reviewboard/static/rb/css/mixins/retina.less
deleted file mode 100644
index 3b96a416cd38573cfe6276a63d83a6fbb3da462e..0000000000000000000000000000000000000000
--- a/reviewboard/static/rb/css/mixins/retina.less
+++ /dev/null
@@ -1,14 +0,0 @@
-// retina.less
-// A helper mixin for applying high-resolution background images (http://www.retinajs.com)
-
-@highdpi: ~"(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)";
-
-.at2x(@path, @w: auto, @h: auto) {
-  background-image: url(@path);
-  @at2x_path: ~`@{path}.replace(/\.\w+$/, function(match) { return "@2x" + match; })`;
-
-  @media @highdpi {
-    background-image: url("@{at2x_path}");
-    background-size: @w @h;
-  }
-}
diff --git a/reviewboard/static/rb/js/common.es6.js b/reviewboard/static/rb/js/common.es6.js
index 2127dfad274e2a2f2b6e3419626e09d9fc4d38a1..b7a335b3e80d1342cdf86a332aea8e6a04c76f0f 100644
--- a/reviewboard/static/rb/js/common.es6.js
+++ b/reviewboard/static/rb/js/common.es6.js
@@ -7,5 +7,5 @@ $(document).ready(function() {
     $('.review-request-link').review_request_infobox();
     $('time.timesince').timesince();
 
-    $('.avatar').retinaAvatar();
+    Djblets.enableRetinaImages();
 });
diff --git a/reviewboard/static/rb/js/reviewRequestPage/views/issueSummaryTableView.es6.js b/reviewboard/static/rb/js/reviewRequestPage/views/issueSummaryTableView.es6.js
index 309f771744d8ac070595fb37c175c40eac1a9106..447d87ff06e2c1d809352ba565e4d460f89836f6 100644
--- a/reviewboard/static/rb/js/reviewRequestPage/views/issueSummaryTableView.es6.js
+++ b/reviewboard/static/rb/js/reviewRequestPage/views/issueSummaryTableView.es6.js
@@ -91,7 +91,7 @@ RB.ReviewRequestPage.IssueSummaryTableView = Backbone.View.extend({
 
         this.$('.user').user_infobox();
         this.$('time.timesince').timesince();
-        this.$('.avatar').retinaAvatar();
+        Djblets.enableRetinaImages(this.$el);
 
         return this;
     },
diff --git a/reviewboard/static/rb/js/reviewRequestPage/views/reviewReplyEditorView.js b/reviewboard/static/rb/js/reviewRequestPage/views/reviewReplyEditorView.js
index 40cc5381adbbbe2602f809916394732b7c04bb99..2d4bb3facd76fb9125a8d1434ec7493668c35c13 100644
--- a/reviewboard/static/rb/js/reviewRequestPage/views/reviewReplyEditorView.js
+++ b/reviewboard/static/rb/js/reviewRequestPage/views/reviewReplyEditorView.js
@@ -14,9 +14,7 @@ RB.ReviewRequestPage.ReviewReplyEditorView = Backbone.View.extend({
         '    <img src="<%- avatarURL %>" width="32" height="32" ',
         '         alt="<%- fullName %>" class="avatar"',
         '         srcset="<%- avatarURL %> 1x',
-        '<% if (avatarURL2x) { %>',
-        '         <%- avatarURL2x %> 2x',
-        '<% }%>',
+        '<% if (avatarURL2x) { %>, <%- avatarURL2x %> 2x<% }%>',
         '         ">',
         '   </div>',
         '   <div class="user-reply-info">',
@@ -204,11 +202,9 @@ RB.ReviewRequestPage.ReviewReplyEditorView = Backbone.View.extend({
             .end()
             .find('time.timesince')
                 .timesince()
-            .end()
-            .find('.avatar')
-                .retinaAvatar()
-            .end()
-            .appendTo(this._$commentsList);
+            .end();
+
+        Djblets.enableRetinaImages($el);
 
         if (options.text) {
             RB.formatText($el.find('.reviewtext'), {
@@ -218,6 +214,8 @@ RB.ReviewRequestPage.ReviewReplyEditorView = Backbone.View.extend({
             });
         }
 
+        $el.appendTo(this._$commentsList);
+
         return $el;
     },
 
diff --git a/reviewboard/static/rb/js/views/fileAttachmentThumbnailView.js b/reviewboard/static/rb/js/views/fileAttachmentThumbnailView.js
index 761443d225e31ec8cd748735c53f1bcc9926081f..72bd3c125c495706e0bb5f020b9d49e0a535143c 100644
--- a/reviewboard/static/rb/js/views/fileAttachmentThumbnailView.js
+++ b/reviewboard/static/rb/js/views/fileAttachmentThumbnailView.js
@@ -328,12 +328,7 @@ RB.FileAttachmentThumbnail = Backbone.View.extend({
         this._$thumbnailContainer.html(
             this.thumbnailContainerTemplate(this.model.attributes));
 
-        _.each(this._$thumbnailContainer.find('img'), function(el) {
-            if (el.hasAttribute('data-at2x')) {
-                this._retinaImage = new RetinaImage(el);
-                el.removeAttribute('data-at2x');
-            }
-        }, this);
+        Djblets.enableRetinaImages(this._$thumbnailContainer);
 
         // Disable tabbing to any <a> elements inside the thumbnail.
         this._$thumbnailContainer.find('a').each(function() {
diff --git a/reviewboard/staticbundles.py b/reviewboard/staticbundles.py
index a2da9ef227394714ac4cbf330f12156755a484cd..4ba3665c15639729e1f28513abdb3ff6871aca29 100644
--- a/reviewboard/staticbundles.py
+++ b/reviewboard/staticbundles.py
@@ -21,7 +21,6 @@ PIPELINE_JAVASCRIPT = dict({
             'lib/js/jquery.timesince.js',
             'lib/js/moment-2.12.0.js',
             'lib/js/moment-timezone-0.5.2.js',
-            'lib/js/retina.js',
             'lib/js/selectize-0.12.1.js',
             'lib/js/ui.autocomplete.js',
             'lib/js/codemirror-5.26.min.js',
diff --git a/reviewboard/templates/base/branding.html b/reviewboard/templates/base/branding.html
index eadccc27519750de3c230dd243bc2438d6f45c98..edbaf9390cf642cf94a949f9f3f36ce4c3b5083b 100644
--- a/reviewboard/templates/base/branding.html
+++ b/reviewboard/templates/base/branding.html
@@ -1,6 +1,9 @@
-{% load staticfiles %}
+{% load djblets_utils staticfiles %}
+
+{% definevar "logo_png" %}{% static "rb/images/logo.png" %}{% enddefinevar %}
+
 <div id="rbinfo">
- <a href="{% url 'root' %}"><img id="logo" src="{% static "rb/images/logo.png" %}" data-at2x="{% static "rb/images/logo@2x.png" %}" alt="" border="0" width="60" height="57" /></a>
+ <a href="{% url 'root' %}"><img id="logo" src="{{logo_png}}" srcset="{{logo_png}} 1x, {% static "rb/images/logo@2x.png" %} 2x" alt="" border="0" width="60" height="57" /></a>
  <h1 id="title">
   <a href="{% url 'root' %}">Review Board</a>
   <span class="version">{{version}}{% if section_title %} - <strong>{{section_title}}</strong>{% endif %}</span>
