Search code examples
javascriptimagecanvascordovaresolution

Cordova iOS - CANVAS get pixel color does not work properly (RETINA size)


In my application the user loads a photo from the library, displays it, and on a tap it gets the tapped pixel's color.

I am using a variation of this snippet (first answer) Get pixel color from canvas, on mouseover to get the pixels color.

This does work if I use non-retina sizes:

var canvas = document.getElementById("myCanvas");
canvas.width = 300;
canvas.height = 480;
canvas.style.width = "300px";
canvas.style.height = "480px";

canvas.drawImage(img, 0, 0); 

camera API:

navigator.camera.getPicture(funcSuccess, funcError, { quality: 100, targetWidth: 300,
                                    targetHeight: 480, destinationType: destinationType.FILE_URI, sourceType: source });

but as soon as I double the size for retina it will always returns a wrong color. (like it expects the image to be 600x960 instead of 300x480 displayed in a 600x960 canvas, ...I don't know)

var canvas = document.getElementById("myCanvas");
canvas.width = 600;
canvas.height = 960;
canvas.style.width = "300px";
canvas.style.height = "480px";

canvas.drawImage(img, 0, 0); 

camera API:

navigator.camera.getPicture(funcSuccess, funcError, { quality: 100, targetWidth: 600,
                                    targetHeight: 960, destinationType: destinationType.FILE_URI, sourceType: source });

I appreciate every response. Thank you.


Solution

  • These

    canvas.width  = 300;
    canvas.height = 480;
    

    is the actual size of the bitmap canvas uses. Everything you do to canvas is relative to these.

    and these

    canvas.style.width  = "300px";
    canvas.style.height = "480px";
    

    are the display size in the browser of the canvas element (not the bitmap). If the size differs from the bitmap size the bitmap size is resized for display purpose only. The bitmap is still of the same size internally. Think of the canvas being equal to an image difference being canvas is editable, image is not.

    This means all operations you do to canvas element has to be scaled to fit the bitmap size in case they are not the same, including mouse/touch positions, because the x and y position you get from the mouse event is relative to the element.

    Ie. clicking at point [600, 600] on the element would also be [600, 600] on the bitmap, but since the bitmap is only half the size the point would be outside the bitmap.

    Likewise, clicking at point [300, 300] on the canvas element would also be [300, 300] on the bitmap, but on the bitmap it would be at the right edge so when the pixel here is changed that pixel is scaled to display size which would end up at the edge too and not where the point was clicked initially.

    Try to scale your click position to get the correct pixel like this:

    Put these in global (and update in canvas resize if needed):

    xScale = canvas.width  / canvas.offsetWidth;  
    yScale = canvas.height / canvas.offsetHeight;
    

    And in your mouse event handler:

    function handleMouse(e) {
    
        var r = canvas.getBoundingClientRect(),
            x = (e.clientX - r.left) * xScale ;
            y = (e.clientY - r.top)  * yScale ;
    
        ... rest of code goes here ...
    }
    

    (it's about the same code for touch, just use the touch point collection instead).