Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scale image in Canvas with offset origin

I've been struggling with this for a few days now. My question is based on code you can find here - http://codepen.io/theOneWhoKnocks/pen/VLExPX. In the example you'll see 3 images, the first scales from the [0,0] origin, the second from the center of the canvas, and the third I want to scale from the center of the offset image.

Basically I want the image to scale up or down, but stay centered on the characters iris. Below you'll find a snippet of code that controls the rendering of the third image.

function renderOffset(){
  var dims = getScaledDims();

  paintBG(ctx3);
  ctx3.drawImage(loadedImg, offsetX, offsetY, dims.width, dims.height);
  drawCenterAxis(ctx3);
}

After much Googling and looking through forums I figure I need to utilize the transformMatrix, but nothing I've tried thus far has worked. I look forward to any ideas or suggestions you may have, and thank you for your time.


Further clarification

I'm creating an image editor. For the particular use case I'm presenting here, a user has moved the image to the left 108px & up 8px.

var offsetX = -108;
var offsetY = 8;

When the user scales the offset image, I want it to scale at the center of the viewable canvas area (the red crosshairs, or in this case the characters iris).


Update

I've updated the codepen link to point to the final code. Below is a list of additions:

  • Added in some of the code mentioned in the accepted answer.
  • Added the ability to drag the image around.
  • Added a visual tracker for the offset.
like image 506
theOneWhoKnocks Avatar asked Feb 09 '23 11:02

theOneWhoKnocks


1 Answers

The trick is understanding the way that scale changes a number of variables. Firstly, it changes how much of the source image is visible on the canvas. Next, this in combination with the desired center-point of the scaling influences where in the image we should start drawing from.

With a scale of 1.0, the number of pixels of the source image shown is equal to the number of pixels that the dst canvas has. I.e, if the canvas is 150x150, we can see 150x150 of the input pixels. If however, the scale is 2.0, then we wish to draw things 2 times the size. This then means that we only wish to display 75x75 pixels of the src image on the 150x150 pixels of the dst canvas. Likewise, if we wish to draw at a scale of 0.5, we should expect to see 300x300 pixels of the src image displayed in the 150x150 of the dst canvas. Perhaps you can see the relationship between scale and canvas size by now.

With this in mind, we can set about determining how much of the src image we wish to see. This is straight-forward:

var srcWidth = canvas.width / scale;
var srcHeight = canvas.height / scale;

Now that we know how much of the image will be shown, we can set about determining where in the image we should start drawing from. Since we have a specified center-point for the scaling, we know that this point should always remain in the center of the canvas.

If we remove scaling from the equation, and use the figures from earlier we can see that we want to display 150x150 pixels of the src image, and that we will need to start drawing 75pixels above and to the left of our center-point. Doing so will draw 150x150 pixels of the source image and place our center-point right smack in the middle of the canvas.

If we then re-consider scaling, we know that we're not always going to be drawing 150x150 pixels of the src image, which means that we can't just blindly start 75pixels left and above our center-point - we will have to scale this 75pixels. Since this 75 pixels is equal to half of the width and half of the height of the portion of the image we'll be displaying, we can work out the point at which to start drawing the image by dividing the srcWidth and srcHeight by 2 and then subtracting this value from the center-point.

Doing so gives us the following expression:

ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);

When I put both of these together into a functioning sample, I ended-up with this:

"use strict";

var imgOriginX = 182, imgOriginY = 66;

function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
    var targetCanvas = byId('canvas3');
    var srcImage = byId('img1');

    drawImageScaled(targetCanvas, srcImage, imgOriginX, imgOriginY)
    drawCrosshair( byId('canvas3') );

    byId('scaleSlider').addEventListener('input', onScaleSliderChange, false);
}

/*
    code for scaling an image about an arbitrary point
*/

// canvas - target canvas element
// image - target canvas element
// imgCenterX - x coord of point of scaling centre-point (unit: pixels)
// imgCenterY - y coord of point of scaling centre-point (unit: pixels)
// scale - 1.0 = 100%
function drawImageScaled(canvas, image, imgCenterX, imgCenterY, scale)
{
    if (scale === undefined)
        scale = 1.0;

    var ctx = canvas.getContext('2d');
    ctx.clearRect(0,0,canvas.width,canvas.height);

    var srcWidth = canvas.width / scale;
    var srcHeight = canvas.height / scale;
    ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);
}

function drawCrosshair(canvas)
{
    var ctx = canvas.getContext('2d');
    var width, height;
    width = canvas.width;
    height = canvas.height;
        ctx.save();
    ctx.beginPath();
        ctx.moveTo(width/2, 0);
        ctx.lineTo(width/2, height);
        ctx.moveTo(0, height/2);
        ctx.lineTo(width, height/2);
    ctx.closePath();
    ctx.strokeStyle = "red";
    ctx.stroke();
    ctx.restore();
}

function onScaleSliderChange(evt)
{
    var curValue = this.value;
    var scale = curValue / 100;
    var tgt, src;

    tgt = byId('canvas3');
    src = byId('img1');
    drawImageScaled(tgt, src, imgOriginX, imgOriginY, scale);
    drawCrosshair(tgt);
}
input[type=range]
{
    width: 18px;
    height: 122px;
    -webkit-appearance: slider-vertical;
}
canvas
{
    border: solid 1px #888;
}

img{ display:none;}
<img id='img1' src='https://i.stack.imgur.com/aFbEw.png'/>
<hr>
<canvas id='canvas3' width=150 height=150>Canvas not supported. :(</canvas>
<input id='scaleSlider' type="range" class="scale-slider js-scaleSlider" min="0" max="200" value="100" orient="vertical"/>
like image 90
enhzflep Avatar answered Feb 12 '23 01:02

enhzflep