How can I get DPI from image in JS?

What you are asking for, to retrieve the physical displayed size in cm of anything digital, can’t be done without a lot of heuristics, that is, we as web-developer have no way to do it for all our visitors.


About Your formula:

DPI – Dots Per Inch.

This is a measure of how many dots can fit in a single inch. This unit is usually for printers or scanners. A monitor however has no dots and thus DPI has no meaning for it.

Digital media like images may embed a DPI information in their metadata, but this is only a hint on either the settings the scanner used when generating the file, either a hint by the author for the intended size it should be printed. Only a few dedicated softwares will make use of this information, and your web browser is not of them.

Digital Pixel – (raster Picture-Cell).

This is the smallest unit of visible information in a raster graphic. These information may be stored in many ways, but is not divisible by other means than creating new, fake data. That’s generally the “px” we refer to in 1920x1080px.
To make it easier to understand, we can take the case of bitmaps, where each digital pixel is represented by its own set of bits in the file, to make it easier again, let’s take the example of an 8bit bitmap image (single-color, generally gray-scale). Here 8 bits are used to represent a single digital pixel (a color dot). For instance we can make a 2x2px bitmap with these information:

[2, 125, 255, 125, 255]
 |   |    |    |    - pixel (2,2) 100% intensity
 |   |    |     - pixel (2,1) 50% intensity
 |   |     - pixel (1,2) 100% intensity
 |    - pixel (1,1) 50% intensity
  - width (nb of digital pixels per line)
/* height can be determined by the `length of data array / width` */

Beware though, all raster images are not bitmaps, and they don’t all have this bits-pixel relation, but they do all have a concept of digital pixel, as in the smallest block of color information they do hold.


With these two concepts clarified, we can explain what your formula means:

const pxTocm = (pixels, DPI) => pixels * 2.54 / DPI;

If we take a 150*150px raster image, and print it using a quite good inkjet-printer with a 300DPI resolution, this formula will correctly give us 1.27cm, or 0.5in

But this only works if we know the printed resolution.


Alright, then let’s just use the screen resolution.

PPI – Pixels Per Inch.

This is what we would need for most monitors. Very similar to DPI, this measures the number of physical pixels a monitor does contain in a single inch.
What a physical pixel for a monitor is vary with the type of monitor used, but for instance in most LCD monitors, one physical pixel is made of three diodes, one Red, one Green, and one Blue, also called sub-PIXELs.

A photograph of sub-PIXEL display elements on a laptop's LCD screen
You can see all three sub-Pixels components composing a single pixel.
Image credits: CC-BY-SA 4.0 Krapteek88 for wikimedia.org

So we could indeed rewrite your formula to be

const pxTocm(pixels, PPI) => pixels * 2.54 / PPI;

But that would work only if the image was displayed at 100%, that is one digital pixel takes up one physical pixel, which is rarely the case on the web, and moreover, we would need to know this PPI value.

From the Web APIs, we have no way of knowing that PPI value. We can’t even be sure we are displaying to a monitor, it could very well be a beam projector, for which the concept of PPI has no meaning whatsoever.

The closest we have is the screen resolution (under Window.screen object, but this will only give us the number of digital pixels the display holds; we still miss the actual physical size of this display.

PPI = Math.hypot(screen.width, screen.height) / physical_size_of_diagonal
// and we don't have any way to find this diagonal...

But what is window.devicePixelRatio I heard about if not my screen’s PPI?

To explain that, we first need to explain what is a CSS px

CSS px – Reads as “pixel” but is not a pixel.

A px is an unit, called by W3C “the magic unit of CSS”. It value depends on the support, and its distance to the viewer’s eyes (!). Here you can find a quite accessible explanation of what is a reference pixel for CSS.
Basically, it should look the same size if you are viewing on a monitor at harm distances, and if you are looking on a phone at closer distance.

I hope you get how hard it is to map this to a real-life measurement.

window.devicePixelRatio

This value represents the number of physical pixels that will get used to display a CSS px.
This could have been useful, because with this value we can map precisely one css px to the number of physical pixels being used.

If we knew what a CSS px really represents i.e (are we on a monitor or a mobile device or some other device?), and what is the current PPI resolution of our user’s monitor (but we don’t), then we could have measured the displayed size on the monitor. (But we can’t).

So yes, we can, on a 96PPI desktop monitor, with no OS zoom, get the kind or formula @JuMoGar provided in their answer (note though they got it wrong):

const CSSpxTocm = (CSSpx) => CSSpx * 2.54 / (96 / devicePixelRatio);

where 96 is the 96PPI hard-coded value CSS px unit uses in their 96th of an inch.

But simply try it on mobile phone, and you’ll see how broken it is. As a fiddle if it’s easier from mobile.

const CSSpxTocm = (CSSpx) => CSSpx * 2.54 / (96 / devicePixelRatio);

console.log(CSSpxTocm(96) + 'cm');
.test {
  border-top: 3px solid blue;
  height: 10px;
  width: 96px;
}
<div class="test px"></div>

On my 125PPI laptop’s monitor (13.3in 1280x1080px), it is already wrong, stating that it is 2.54cm, while it is in fact around 2.2cm, but on my Galaxy S6 phone, it returns 10.16cm while IRL, it is roughly 1.7cm.

Why? Because the screen has ~577 PPI, but still, it uses only one physical pixel per CSS px. This means the devicePixelRatio is still 1, but the PPI being so far from expected 96, this formula returns absurdities. (The diagonal of an S6 screen is 5.1in, ~12.9cm)

Leave a Comment