Search code examples
htmlcssfirefoxseamonkey

How do I prevent Mozilla from up-scaling images on HiDPI displays?


When viewing a raster image, all more or less recent Mozilla products (e. g.: Firefox and SeaMonkey) transform it into a full HTML document, e. g.:

<html>
  <head>
    <meta name="viewport" content="width=device-width; height=device-height;">
    <link rel="stylesheet" href="resource://gre/res/ImageDocument.css">
    <link rel="stylesheet" href="resource://gre/res/TopLevelImageDocument.css">
    <link rel="stylesheet" href="chrome://global/skin/media/TopLevelImageDocument.css">
    <title>googlelogo_color_272x92dp.png (PNG Image, 544&nbsp;×&nbsp;184 pixels)</title>
  </head>
  <body>
    <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" alt="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png">
  </body>
</html>

The content of the default CSS styles can be viewed here:

The problem is, on HiDPI displays, low-resolution images often get up-scaled (for instance, on a 4k display they're enlarged by a factor of x1.5).

Which extra styles do I need to apply to the generated HTML in order for images to be always displayed in their original size (i. e. 1:1)?

I'm looking for the proper way to customize either userContent.css or userChrome.css.

More on userChrome.css:


Solution

  • As you've indicated you are solely interested in a CSS solution, I will not be including a JavaScript alternative.

    Using CSS only will inherently restrict you to an enumeration of DPIs, as CSS doesn't support any way to get the device pixel ratio. In the future, there may be an environment variable set, though I'm not aware of any such proposal.

    In my approach, I've opted to use min-resolution over resolution, as this will at least select something close if the exact DPI isn't matched. For this to work, you must not be setting the width property of the img elsewhere. height will be automatically calculated, as is the default behavior.

    @media (min-resolution: 1.5dppx) {
      img {
        transform: scale(calc(1 / 1.5), calc(1 / 1.5));
      }
    }
    
    @media (min-resolution: 2dppx) {
      img {
        transform: scale(calc(1 / 2), calc(1 / 2));
      }
    }
    
    @media (min-resolution: 2.5dppx) {
      img {
        transform: scale(calc(1 / 2.5), calc(1 / 2.5));
      }
    }
    
    @media (min-resolution: 3dppx) {
      img {
        transform: scale(calc(1 / 3), calc(1 / 3));
      }
    }
    <meta name="viewport" content="width=device-width; height=device-height;">
    <link rel="stylesheet" href="resource://gre/res/ImageDocument.css">
    <link rel="stylesheet" href="resource://gre/res/TopLevelImageDocument.css">
    <link rel="stylesheet" href="chrome://global/skin/media/TopLevelImageDocument.css">
    
    <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" alt="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png">

    I've only included a few here, but the general idea is pretty straightforward — scale the image down by the same amount the browser is scaling it up.

    If Sass is your thing, there's a simple way to loop over your supported DPIs and generate the resulting code. A similar result can be obtained in PostCSS with a couple plugins.

    $resolutions: 1.5 2 2.5 3
    
    img
      @each $res in $resolutions
        @if $res != 1
          @media (min-resolution: #{$res}dppx)
            transform: scale(1 / $res, 1 / $res)