diff --git a/djblets/integrations/templates/integrations/integration_list.html b/djblets/integrations/templates/integrations/integration_list.html
index 3db2720702f03ebbcad9ef941c6293311f792a93..a3e67a5eb3a6415a4a124438585f9321aa633415 100644
--- a/djblets/integrations/templates/integrations/integration_list.html
+++ b/djblets/integrations/templates/integrations/integration_list.html
@@ -1,5 +1,5 @@
 {% extends "djblets_forms/admin/base_site.html" %}
-{% load djblets_utils i18n integrations pipeline staticfiles %}
+{% load djblets_images djblets_utils i18n integrations pipeline staticfiles %}
 
 
 {% block title %}
@@ -26,12 +26,9 @@
 {%  for integration_info in integrations %}
     <li>
 {%   if integration_info.icons.1x %}
-     {# TODO: Move to the srcset filter, once it's landed. #}
      <img class="integration-icon"
           src="{{integration_info.icons.1x}}"
-{%    if integration_info.icons.2x %}
-          data-at2x="{{integration_info.icons.2x}}"
-{%    endif %}
+          srcset="{% srcset integration_info.icons %}"
           width="48" height="48" alt="" />
 {%   endif %}
      <div class="integration-details">
diff --git a/djblets/static/djblets/css/datagrid.less b/djblets/static/djblets/css/datagrid.less
index ee4b27b86089560b7ccae55e50406795933deea3..3130e63d80f8aaf0288bbdee6362eeb9a02cffd4 100644
--- a/djblets/static/djblets/css/datagrid.less
+++ b/djblets/static/djblets/css/datagrid.less
@@ -1,4 +1,4 @@
-@import (reference) "retina.less";
+@import (reference) "mixins/retina.less";
 
 
 @media screen and (max-width: 720px),
@@ -391,8 +391,7 @@
   text-indent: -99999px;
   vertical-align: middle;
 
-  background-image: url("../images/datagrid/icons.png");
-  .at2x('../images/datagrid/icons.png', 66px, 16px);
+  .retina('../images/datagrid/icons.png', 66px 16px, @has-svg: true);
 
   // For IE7
   zoom: 1;
diff --git a/djblets/static/djblets/css/mixins/retina.less b/djblets/static/djblets/css/mixins/retina.less
new file mode 100644
index 0000000000000000000000000000000000000000..bfae564e39c290168a8eab6ada7702267ae3b7d4
--- /dev/null
+++ b/djblets/static/djblets/css/mixins/retina.less
@@ -0,0 +1,95 @@
+/**
+ * Mixins for adding high-dpi (Retina) images to an element.
+ *
+ * This is somewhat based off the Retina stylesheet from
+ * https://github.com/strues/retinajs/blob/master/src/retina.less. It has
+ * been modified for SVG support and backwards-compatibility.
+ */
+
+/**
+ * Enable one or more high-DPI images for an element.
+ *
+ * This allows for ``@2x``, ``@3x``, etc. and ``.svg`` images to be used for
+ * for an element based on the DPI of the screen. By default, this will
+ * attempt to find a ``@2x`` PNG, but ``@3x`` and higher can be used by
+ * setting ``@max-ratio``.
+ *
+ * SVGs can also be used for any resolutions higher than the max ratio by
+ * setting ``@has-svg: true``. This expects a ``.svg`` file to be avalable.
+ *
+ * Args:
+ *     path (string):
+ *         The path to the main image file.
+ *
+ *     size (*):
+ *         The size used for the main image file. This is generally in
+ *         ``width height`` form, but any value allowed by
+ *         ``background-size`` can be used.
+ *
+ *     max-ratio (number):
+ *         The maximum pixel ratio for raster images. All ratios 2 through this
+ *         this number will be used. CSS rules will be added for each that look
+ *         for ``@2x``, ``@3x``, etc. up to the specified ratio.
+ *
+ *     has-svg (boolean):
+ *         Whether a SVG file exists and should be used. If ``true``, any
+ *         screen DPIs higher than ``max-ratio`` will make use of the SVG
+ *         file. To enable SVG for 2x, set ``@max-ratio: 1``.
+ */
+.retina(@path, @size: auto auto, @max-ratio: 2, @has-svg: false) {
+  /* Set up the default image and size. */
+  background-image: url(@path);
+  background-size: @size;
+
+  .at2x-query(@retina-path) {
+    @media (-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) {
+      background: url(@retina-path);
+      background-size: @size;
+    }
+  }
+
+  .higher-dpi-queries(@retina-path, @ratio) {
+    @media (-webkit-min-device-pixel-ratio: @ratio),
+           (min-resolution: (@ratio * 96dpi)) {
+      background: url(@retina-path);
+      background-size: @size;
+    }
+  }
+
+  /*
+   * If there are 2 or more ratio levels, loop through and create media
+   * selectors for each.
+   */
+  & when (@max-ratio >= 2) {
+    .at2x-query(~`@{path}.replace(/\.\w+$/, function(ext) { return '@2x' + ext; })`);
+
+    .loop(@ratio) when (@ratio <= @max-ratio) {
+      .higher-dpi-queries(
+        ~`@{path}.replace(/\.\w+$/, function(ext) { return '@@{ratio}x' + ext; })`,
+        @ratio);
+
+      .loop(@ratio + 1);
+    }
+
+    .loop(2);
+  }
+
+  /*
+   * If SVG is enabled, create a media selector for it based on the max
+   * ratio.
+   */
+  & when (@has-svg = true) {
+    @svg-path: ~`@{path}.replace(/\.\w+$/, function() { return '.svg'; })`;
+
+    & when (@max-ratio = 1) {
+      .at2x-query(@svg-path);
+    }
+
+    & when (@max-ratio >= 2) {
+      .higher-dpi-queries(@svg-path, @max-ratio + 1);
+    }
+  }
+}
diff --git a/djblets/static/djblets/css/retina.less b/djblets/static/djblets/css/retina.less
index 252c3547d49fd722e1a3032730a5aced86367472..cd1e123046c452387b6a94ed93f2a4f07bfa6151 100644
--- a/djblets/static/djblets/css/retina.less
+++ b/djblets/static/djblets/css/retina.less
@@ -1,15 +1,33 @@
-// retina.less
-// A helper mixin for applying high-resolution background images (http://www.retinajs.com)
+/**
+ * Backwards-compatibility file for retina.less.
+ *
+ * Callers should use mixins/retina.less instead.
+ *
+ * .. deprecated:: 1.0
+ */
 
-@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)";
+@import (reference) "mixins/retina.less";
 
-.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;
-  }
+/*
+ * Enable support for a 2x Retina image for an element.
+ *
+ * This is a backwards-compatible mixin for supporting ``@2x`` files for
+ * an image. It will be removed in a future release. It's recommended that
+ * callers use ``.retina()`` from ``mixins/retina.less` instead.
+ *
+ * .. deprecated:: 1.0
+ *
+ * Args:
+ *     path (string):
+ *         The path to the main image file.
+ *
+ *     w (number):
+ *         The width of the main image file.
+ *
+ *     h (number):
+ *         The height of the main image file.
+ */
+.at2x(@path, @w: auto, @h: auto) {
+  .retina(@path, @w @h);
 }
-
diff --git a/djblets/static/djblets/js/jquery.gravy.retina.js b/djblets/static/djblets/js/jquery.gravy.retina.js
index 667efa81428b5448be5c4b43ab84680532a97f54..83ce9317fcb175e0fe9dd3270766e157bf4d5f4c 100644
--- a/djblets/static/djblets/js/jquery.gravy.retina.js
+++ b/djblets/static/djblets/js/jquery.gravy.retina.js
@@ -22,22 +22,26 @@
  */
 (function($) {
 
-/* Whether or not the browser supports the ``srcset`` attribute.
+
+/**
+ * Whether or not the browser supports the ``srcset`` attribute.
  *
  * All versions of IE and Edge do not support this attribute so we need to
  * polyfill for them.
  */
-var supportsSourceSet = ($('<img src="" />').get().srcset === '');
+var supportsSourceSet = ($('<img src="" />')[0].srcset === '');
 
-/*
+
+/**
  * Parse the ``srcset`` attribute.
  *
  * Args:
- *     srcset (String):
+ *     srcset (string):
  *         The source set, as a string of comma-separated URLs and descriptors.
  *
  * Returns:
- *     Object: A mapping of descriptors to URLs.
+ *     object:
+ *     A mapping of descriptors to URLs.
  */
 function parseSourceSet(srcset) {
     var urls = {},
@@ -63,24 +67,59 @@ function parseSourceSet(srcset) {
     return urls;
 }
 
-/*
- * If appropriate, reload avatar <img> tags with retina resolution equivalents.
+
+/**
+ * Enable Retina images on older browsers.
+ *
+ * This will process a list of image elements containing ``srcset``
+ * attributes and set the image's URL to the ``srcset`` value best matching
+ * the current pixel ratio.
+ *
+ * If the browser natively supports ``srcset``, or the screen's pixel ratio
+ * is 1 (non-Retina), this is a no-op.
+ *
+ * This may be called repeatedly. Images are only updated if the pixel ratio
+ * has changed since last called.
+ *
+ * Args:
+ *     $container (jQuery, optional):
+ *         The container element that contains images somewhere in its tree.
+ *         This defaults to ``document.body``.
+ *
+ *     $selector (string, optional):
+ *         The selector to match images. This defaults to ``img``.
  */
-$.fn.retinaAvatar = function() {
+Djblets.enableRetinaImages = function($container, selector) {
     var pixelRatio = window.devicePixelRatio;
 
-    if (pixelRatio > 1 && !supportsSourceSet) {
-        /*
-         * It is more useful to provide a 2x avatar on a 1.5 pixel ratio than
-         * to provide a 1x avatar.
-          */
-        pixelRatio = Math.ceil(pixelRatio);
+    if (pixelRatio === 1 || supportsSourceSet) {
+        return;
+    }
+
+    /*
+     * It is more useful to provide a 2x image on a 1.5 pixel ratio than
+     * to provide a 1x image.
+      */
+    pixelRatio = Math.ceil(pixelRatio);
 
-        $(this).each(function() {
-            var $el = $(this),
-                urls = parseSourceSet($el.attr('srcset') || ''),
-                descriptor,
-                url;
+    if (!$container) {
+        $container = $(document.body);
+    }
+
+    if (!selector) {
+        selector = 'img';
+    }
+
+    $container.find(selector).each(function() {
+        var $el = $(this),
+            srcset = $el.attr('srcset'),
+            shimmedAt = $el.data('srcset-shimmed-at'),
+            urls,
+            descriptor,
+            url;
+
+        if (srcset && shimmedAt !== pixelRatio) {
+            urls = parseSourceSet(srcset);
 
             for (descriptor = pixelRatio; descriptor > 0; descriptor--) {
                 url = urls[descriptor + 'x'];
@@ -88,20 +127,25 @@ $.fn.retinaAvatar = function() {
                 if (url !== undefined) {
                     $el
                         .attr('src', url)
-                        .addClass('avatar-at' + descriptor + 'x');
+                        .data('srcset-shimmed-at', pixelRatio);
 
-                    return;
+                    break;
                 }
             }
-        });
-    }
-
-    return this;
+        }
+    });
 };
 
-/*
- * If appropriate, reload gravatar <img> tags with retina resolution
- * equivalents.
+
+/**
+ * Enable Retina support for Gravatar image elements.
+ *
+ * This will parse the Gravatar URLs, replacing it with one using a size
+ * more applicable to the screen's pixel ratio.
+ *
+ * Returns:
+ *     jQuery:
+ *     This element, for chaining.
  */
 $.fn.retinaGravatar = function() {
     if (window.devicePixelRatio > 1) {
@@ -119,17 +163,19 @@ $.fn.retinaGravatar = function() {
 };
 
 
-/*
+/**
  * Return a Gravatar URL most appropriate for the display.
  *
  * If on a Retina or other high-DPI display, a higher-resolution Gravatar
  * will be returned.
  *
  * Args:
- *     url (String): The URL to the Gravatar.
+ *     url (string):
+ *         The URL to the Gravatar.
  *
  * Returns:
- *     String: The URL to the Gravatar best matching the current display.
+ *     string:
+ *     The URL to the Gravatar best matching the current display.
  */
 Djblets.getGravatarForDisplay = function(url) {
     if (window.devicePixelRatio > 1) {
@@ -165,5 +211,3 @@ Djblets.getGravatarForDisplay = function(url) {
 
 
 })(jQuery);
-
-// vim: set et:
