Repository: GoogleChrome/lighthouse (js)

Issue:
uses-responsive-images only cares about savings not about correct use of responsive images
The `uses-responsive-images` check only checks whether a too large image is used. This means that it only catches cases in which the used image is way bigger than the actual display size. This typically happens when developers just shove a @2x or larger version of the image without using a `srcset`.

The part that is missing is when the image is too small compared to the display size. I.e, no @2x version is in use.

Basically I'm suggesting to add something along the lines:

```js
const DPR = artifacts.ViewportDimensions.devicePixelRatio;
const TOLERANCE = 0.9; // This is to not produce that many violations, I'd like it to be set to 1.
const bigEnough = image.displayedHeight <= image.naturalHeight / (DPR * TOLERANCE) && image.displayedWidth <= image.naturalWidth / (DPR * TOLERANCE);
```

I understand, that doing in that way may change the scope of the audit, as it is currently part of the byte efficiency category. So feel free to recategorize this as a feature request.

Pull request description:
core(image-size-responsive): quantize DPRs

<!-- Thank you for submitting a pull request! -->

**Summary**
<!-- What kind of change does this PR introduce? -->
<!-- Is this a bugfix, feature, refactoring, build related change, etc? -->
<!-- Describe the need for this change -->
In some contexts lighthouse uses a DPR of 2.625, which is a rather uncommon DPR, so to minimize the number of failures, we will quantize the original DPR to the lowest integer (or 1.5 if in between 1.5 and 2).

<!-- Link any documentation or information that would help understand this change -->

**Related Issues/PRs**
<!-- Provide any additional information we might need to understand the pull request -->
Fixes #10434 and #10802


Patch:
diff --git a/lighthouse-core/audits/image-size-responsive.js b/lighthouse-core/audits/image-size-responsive.js
index a0bfc79b4..29800337d 100644
--- a/lighthouse-core/audits/image-size-responsive.js
+++ b/lighthouse-core/audits/image-size-responsive.js
@@ -16,12 +16,12 @@ const i18n = require('../lib/i18n/i18n.js');
 
 const UIStrings = {
   /** Title of a Lighthouse audit that provides detail on the size of visible images on the page. This descriptive title is shown to users when all images have correct sizes. */
-  title: 'Displays images with appropriate size',
+  title: 'Serves images with appropriate resolution',
   /** Title of a Lighthouse audit that provides detail on the size of visible images on the page. This descriptive title is shown to users when not all images have correct sizes. */
-  failureTitle: 'Displays images with inappropriate size',
+  failureTitle: 'Serves images with low resolution',
   /** Description of a Lighthouse audit that tells the user why they should maintain an appropriate size for all images. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
   description: 'Image natural dimensions should be proportional to the display size and the ' +
-    'pixel ratio to maximize image clarity. [Learn more](https://web.dev/image-size-responsive).',
+    'pixel ratio to maximize image clarity. [Learn more](https://web.dev/serve-responsive-images/).',
   /**  Label for a column in a data table; entries in the column will be a string representing the displayed size of the image. */
   columnDisplayed: 'Displayed size',
   /**  Label for a column in a data table; entries in the column will be a string representing the actual size of the image. */
@@ -137,8 +137,9 @@ function allowedImageSize(displayedWidth, displayedHeight, DPR) {
   if (displayedWidth > SMALL_IMAGE_THRESHOLD || displayedHeight > SMALL_IMAGE_THRESHOLD) {
     factor = LARGE_IMAGE_FACTOR;
   }
-  const width = Math.ceil(factor * DPR * displayedWidth);
-  const height = Math.ceil(factor * DPR * displayedHeight);
+  const requiredDpr = quantizeDpr(DPR);
+  const width = Math.ceil(factor * requiredDpr * displayedWidth);
+  const height = Math.ceil(factor * requiredDpr * displayedHeight);
   return [width, height];
 }
 
@@ -237,5 +238,24 @@ class ImageSizeResponsive extends Audit {
   }
 }
 
+/**
+ * Return a quantized version of the DPR.
+ *
+ * This is to relax the required size of the image, as there are some densities that are not that
+ * common, and the default DPR used in some contexts by lightouse is 2.625.
+ *
+ * @param {number} dpr
+ * @return {number}
+ */
+function quantizeDpr(dpr) {
+  if (dpr >= 2) {
+    return Math.floor(dpr);
+  }
+  if (dpr >= 1.5) {
+    return 1.5;
+  }
+  return 1.0;
+}
+
 module.exports = ImageSizeResponsive;
 module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json
index 52d1518ca..d0217eb57 100644
--- a/lighthouse-core/lib/i18n/locales/en-US.json
+++ b/lighthouse-core/lib/i18n/locales/en-US.json
@@ -777,13 +777,13 @@
     "message": "Expected size"
   },
   "lighthouse-core/audits/image-size-responsive.js | description": {
-    "message": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn more](https://web.dev/image-size-responsive)."
+    "message": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn more](https://web.dev/serve-responsive-images/)."
   },
   "lighthouse-core/audits/image-size-responsive.js | failureTitle": {
-    "message": "Displays images with inappropriate size"
+    "message": "Serves images with low resolution"
   },
   "lighthouse-core/audits/image-size-responsive.js | title": {
-    "message": "Displays images with appropriate size"
+    "message": "Serves images with appropriate resolution"
   },
   "lighthouse-core/audits/installable-manifest.js | description": {
     "message": "Browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://web.dev/installable-manifest)."
diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json
index d145ee1a1..702cb87a9 100644
--- a/lighthouse-core/lib/i18n/locales/en-XL.json
+++ b/lighthouse-core/lib/i18n/locales/en-XL.json
@@ -777,13 +777,13 @@
     "message": "Êx́p̂éĉt́êd́ ŝíẑé"
   },
   "lighthouse-core/audits/image-size-responsive.js | description": {
-    "message": "Îḿâǵê ńât́ûŕâĺ d̂ím̂én̂śîón̂ś ŝh́ôúl̂d́ b̂é p̂ŕôṕôŕt̂íôńâĺ t̂ó t̂h́ê d́îśp̂ĺâý ŝíẑé âńd̂ t́ĥé p̂íx̂él̂ ŕât́îó t̂ó m̂áx̂ím̂íẑé îḿâǵê ćl̂ár̂ít̂ý. [L̂éâŕn̂ ḿôŕê](https://web.dev/image-size-responsive)."
+    "message": "Îḿâǵê ńât́ûŕâĺ d̂ím̂én̂śîón̂ś ŝh́ôúl̂d́ b̂é p̂ŕôṕôŕt̂íôńâĺ t̂ó t̂h́ê d́îśp̂ĺâý ŝíẑé âńd̂ t́ĥé p̂íx̂él̂ ŕât́îó t̂ó m̂áx̂ím̂íẑé îḿâǵê ćl̂ár̂ít̂ý. [L̂éâŕn̂ ḿôŕê](https://web.dev/serve-responsive-images/)."
   },
   "lighthouse-core/audits/image-size-responsive.js | failureTitle": {
-    "message": "D̂íŝṕl̂áŷś îḿâǵêś ŵít̂h́ îńâṕp̂ŕôṕr̂íât́ê śîźê"
+    "message": "Ŝér̂v́êś îḿâǵêś ŵít̂h́ l̂óŵ ŕêśôĺût́îón̂"
   },
   "lighthouse-core/audits/image-size-responsive.js | title": {
-    "message": "D̂íŝṕl̂áŷś îḿâǵêś ŵít̂h́ âṕp̂ŕôṕr̂íât́ê śîźê"
+    "message": "Ŝér̂v́êś îḿâǵêś ŵít̂h́ âṕp̂ŕôṕr̂íât́ê ŕêśôĺût́îón̂"
   },
   "lighthouse-core/audits/installable-manifest.js | description": {
     "message": "B̂ŕôẃŝér̂ś ĉán̂ ṕr̂óâćt̂ív̂él̂ý p̂ŕôḿp̂t́ ûśêŕŝ t́ô ád̂d́ ŷóûŕ âṕp̂ t́ô t́ĥéîŕ ĥóm̂éŝćr̂éêń, ŵh́îćĥ ćâń l̂éâd́ t̂ó ĥíĝh́êŕ êńĝáĝém̂én̂t́. [L̂éâŕn̂ ḿôŕê](https://web.dev/installable-manifest)."


Test patch:
diff --git a/lighthouse-core/test/audits/image-size-responsive-test.js b/lighthouse-core/test/audits/image-size-responsive-test.js
index e800220de..2a4d80c72 100644
--- a/lighthouse-core/test/audits/image-size-responsive-test.js
+++ b/lighthouse-core/test/audits/image-size-responsive-test.js
@@ -318,6 +318,36 @@ describe('Images: size audit', () => {
         devicePixelRatio: 2,
       });
     });
+
+    describe('DPR = 2.625', () => {
+      testImage('is an icon with right size', {
+        score: 1,
+        clientSize: [64, 64],
+        naturalSize: [128, 128],
+        devicePixelRatio: 2.625,
+      });
+
+      testImage('is an icon with an invalid size', {
+        score: 0,
+        clientSize: [64, 64],
+        naturalSize: [127, 127],
+        devicePixelRatio: 2.625,
+      });
+
+      testImage('has right size', {
+        score: 1,
+        clientSize: [65, 65],
+        naturalSize: [98, 98],
+        devicePixelRatio: 2.625,
+      });
+
+      testImage('has an invalid size', {
+        score: 0,
+        clientSize: [65, 65],
+        naturalSize: [97, 97],
+        devicePixelRatio: 2.625,
+      });
+    });
   });
 
   it('de-dupes images', () => {
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 3192d4b61..9339ee013 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -632,8 +632,8 @@
     },
     "image-size-responsive": {
       "id": "image-size-responsive",
-      "title": "Displays images with inappropriate size",
-      "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn more](https://web.dev/image-size-responsive).",
+      "title": "Serves images with low resolution",
+      "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn more](https://web.dev/serve-responsive-images/).",
       "score": 0,
       "scoreDisplayMode": "binary",
       "details": {