Detecting the system DPI/PPI from JS/CSS?

JavascriptCssMobileDpiPpi

Javascript Problem Overview


I'm working on a kind of unique app which needs to generate images at specific resolutions according to the device they are displayed on. So the output is different on a regular Windows browser (96ppi), iPhone (163ppi), Android G1 (180ppi), and other devices. I'm wondering if there's a way to detect this automatically.

My initial research seems to say no. The only suggestion I've seen is to make an element whose width is specified as "1in" in CSS, then check its offsetWidth (see also https://stackoverflow.com/q/476815/698168). Makes sense, but iPhone is lying to me with that technique, saying it's 96ppi.

Another approach might be to get the dimensions of the display in inches and then divide by the width in pixels, but I'm not sure how to do that either.

Javascript Solutions


Solution 1 - Javascript

<div id='testdiv' style='height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;'></div>
<script type='text/javascript'>
  var devicePixelRatio = window.devicePixelRatio || 1;
  dpi_x = document.getElementById('testdiv').offsetWidth * devicePixelRatio;
  dpi_y = document.getElementById('testdiv').offsetHeight * devicePixelRatio;
  
  console.log(dpi_x, dpi_y);
</script>

grabbed from here http://www.infobyip.com/detectmonitordpi.php. Works on mobile devices! (android 4.2.2 tested)

Solution 2 - Javascript

I came up with a way that doesn't require the DOM... at all

The DOM can be messy, requiring you to append stuff to the body without knowing what stuff is going on with width: x !important in your stylesheet. You would also have to wait for the DOM to be ready to use...

/**
 * Binary search for a max value without knowing the exact value, only that it can be under or over
 * It dose not test every number but instead looks for 1,2,4,8,16,32,64,128,96,95 to figure out that
 * you thought about #96 from 0-infinity
 *
 * @example findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
 * @author Jimmy Wärting
 * @see {@link https://stackoverflow.com/a/35941703/1008999}
 * @param {function} fn       The function to run the test on (should return truthy or falsy values)
 * @param {number}   start=1  Where to start looking from
 * @param {function} _        (private)
 * @returns {number}          Intenger
 */
function findFirstPositive (f,b=1,d=(e,g,c)=>g<e?-1:0<f(c=e+g>>>1)?c==e||0>=f(c-1)?c:d(e,c-1):d(c+1,g)) {
  for (;0>=f(b);b<<=1);return d(b>>>1,b)|0
}

var dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)

console.log(dpi)

Solution 3 - Javascript

There is the resolution CSS media query — it allows you to limit CSS styles to specific resolutions:

However, it’s only supported by Firefox 3.5 and above, Opera 9 and above, and IE 9. Other browsers won’t apply your resolution-specific styles at all (although I haven’t checked non-desktop browsers).

Solution 4 - Javascript

Here is what works for me (but didn't test it on mobile phones):

<body><div id="ppitest" style="width:1in;visible:hidden;padding:0px"></div></body>

Then I put in the .js: screenPPI = document.getElementById('ppitest').offsetWidth;

This got me 96, which corresponds to my system's ppi.

Solution 5 - Javascript

DPI is by definition tied to the physical size of the display. So you won't be able to have the real DPI without knowing exactly the hardware behind.

Modern OSes agreed on a common value in order to have compatible displays: 96 dpi. That's a shame but that's a fact.

You will have to rely on sniffing in order to be able to guess the real screen size needed to compute the resolution (DPI = PixelSize / ScreenSize).

Solution 6 - Javascript

I also needed to display the same image at the same size at different screen dpi but only for Windows IE. I used:

<img src="image.jpg" style="
height:expression(scale(438, 192));
width:expression(scale(270, 192))" />

function scale(x, dpi) {

// dpi is for orignal dimensions of the image
return x * screen.deviceXDPI/dpi;

}

In this case the original image width/height are 270 and 438 and the image was developed on 192dpi screen. screen.deviceXDPI is not defined in Chrome and the scale function would need to be updated to support browsers other than IE

Solution 7 - Javascript

The reply from @Endless is pretty good, but not readable at all, this is a similar approche with fixed min/max (it should be good ones)

var dpi = (function () {
    for (var i = 56; i < 2000; i++) {
        if (matchMedia("(max-resolution: " + i + "dpi)").matches === true) {
            return i;
        }
    }
    return i;
})();

matchMedia is now well supported and should give good result, see http://caniuse.com/#feat=matchmedia

Be careful the browser won't give you the exact screen dpi but only an approximation

Solution 8 - Javascript

function getPPI(){
  // create an empty element
  var div = document.createElement("div");
  // give it an absolute size of one inch
  div.style.width="1in";
  // append it to the body
  var body = document.getElementsByTagName("body")[0];
  body.appendChild(div);
  // read the computed width
  var ppi = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
  // remove it again
  body.removeChild(div);
  // and return the value
  return parseFloat(ppi);
} 

(From VodaFone)

Solution 9 - Javascript

I think your best approach is to combine the suggestion of the "sniffer" image with a matrix of known DPIs for devices (via user agent and other methods). It won't be exact and will be a pain to maintain, but without knowing more about the app you're trying to make that's the best suggestion I can offer.

Solution 10 - Javascript

Reading through all these responses was quite frustrating, when the only correct answer is: No, it is not possible to detect the DPI from JavaScript/CSS. Often, the operating system itself does not even know the DPI of the connected screens (and reports it as 96 dpi, which I suspect might be the reason why many people seem to believe that their method of detecting DPI in JavaScript is accurate). Also, when multiple screens are connected to a device forming a unified display, the viewport and even a single DOM element can span multiple screens with different DPIs, which would make these calculations quite challenging.

Most of the methods described in the other answers will almost always result in an output of 96 dpi, even though most screens nowadays have a higher DPI. For example, the screen of my ThinkPad T14 has 157 dpi, according to this calculator, but all the methods described here and my operating system tell me that it has 96 dpi.

Your idea of assigning a CSS width of 1in to a DOM element does not work. It seems that a CSS inch is defined as 96 CSS pixels. By my understanding, a CSS pixel is defined as a pixel multiplied by the devicePixelRatio, which traditionally is 1, but can be higher or lower depending on the zoom level configured in the graphical interface of the operating system and in the browser.

It seems that the approach of using resolution media queries produces at least some results on a few devices, but they are often still off by a factor of more than 2. Still, on most devices this approach also results in a value of 96 dpi.

Solution 11 - Javascript

Can't you do anything else? For instance, if you are generating an image to be recognized by a camera (i.e. you run your program, swipe your cellphone across a camera, magic happens), can't you use something size-independent?

If this is an application to be deployed in controlled environments, can you provide a calibration utility? (you could make something simple like print business cards with a small ruler in it, use it during the calibration process).

Solution 12 - Javascript

I just found this link: http://dpi.lv/. Basically it is a webtool to discover the client device resolution, dpi, and screen size.

I visited on my computer and mobile phone and it provides the correct resolution and DPI for me. There is a github repo for it, so you can see how it works.

Solution 13 - Javascript

Generate a list of known DPI: https://stackoverflow.com/a/6793227

Detect the exact device. Using something like:

navigator.userAgent.toLowerCase();

For example, when detecting mobile:

window.isMobile=/iphone|ipod|ipad|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec/i.test(navigator.userAgent.toLowerCase());

And profit!

Solution 14 - Javascript

Readable code from @Endless reply:

const dpi = (function () {
	let i = 1;
	while ( !hasMatch(i) ) i *= 2;

	function getValue(start, end) {
		if (start > end) return -1;
		let average = (start + end) / 2;
		if ( hasMatch(average) ) {
        	if ( start == average || !hasMatch(average - 1) ) {
            	return average;
        	} else {
            	return getValue(start, average - 1);
        	}
		} else {
			return getValue(average + 1, end);
		}
	}

	function hasMatch(x) {
		return matchMedia(`(max-resolution: ${x}dpi)`).matches;
	}

	return getValue(i / 2, i) | 0;
})();

Solution 15 - Javascript

Maybe I'm a little bit steering off this topic...
I was working on a html canvas project, which was intended to provide a drawing canvas for people to draw lines on. I wanted to set canvas's size to 198x280mm which is fit for A4 printing. So I started to search for a resolution to convert 'mm' to 'px' and to display the canvas suitably on both PC and mobile.
I tried solution from @Endless ,code as:

const canvas = document.getElementById("canvas");
function findFirstPositive(b, a, i, c) {
  c=(d,e)=>e>=d?(a=d+(e-d)/2,0<b(a)&&(a==d||0>=b(a-1))?a:0>=b(a)?c(a+1,e):c(d,a-1)):-1
  for (i = 1; 0 >= b(i);) i *= 2
  return c(i / 2, i)|0
}
const dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
let w = 198 * dpi / 25.4;
let h = 280 * dpi / 25.4;
canvas.width = w;
canvas.height = h;

It worked well on PC browser, showing dpi=96 and size was 748x1058 px;work well on PC
However turned to mobile devices, it was much larger than I expected: size: 1902x2689 px.can't work on mobile
After searching for keywords like devicePixelRatio, I suddenly realize that, I don't actually need to show real A4 size on mobile screen (under which situation it's actually hard to use), I just need the canvas's size fit for printing, so I simply set the size to:

let [w,h] = [748,1058];
canvas.width = w;
canvas.height = h;

...and it is well printed:well printed

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJosh SantangeloView Question on Stackoverflow
Solution 1 - JavascriptMaxXx1313View Answer on Stackoverflow
Solution 2 - JavascriptEndlessView Answer on Stackoverflow
Solution 3 - JavascriptPaul D. WaiteView Answer on Stackoverflow
Solution 4 - JavascriptjohnView Answer on Stackoverflow
Solution 5 - JavascriptVincent RobertView Answer on Stackoverflow
Solution 6 - JavascriptFarid ZView Answer on Stackoverflow
Solution 7 - JavascriptjrmgxView Answer on Stackoverflow
Solution 8 - JavascriptTJS101View Answer on Stackoverflow
Solution 9 - JavascriptFriendOfFutureView Answer on Stackoverflow
Solution 10 - JavascriptcdauthView Answer on Stackoverflow
Solution 11 - JavascriptalexView Answer on Stackoverflow
Solution 12 - JavascriptAdamView Answer on Stackoverflow
Solution 13 - JavascripthaelmicView Answer on Stackoverflow
Solution 14 - JavascriptАлексей ЖуковView Answer on Stackoverflow
Solution 15 - JavascriptrookieView Answer on Stackoverflow