1

I'm trying to use a smaller canvas as a viewport for another much bigger canvas. I really like the approach used in this the solution on a similar question.

He basically uses CanvasRenderingContext2D.drawImage() to "crop" the buffer/offset canvas and then displays that portion of the image on the smaller canvas (viewport). I'm trying to implement a simpler version of the given solution in this fiddle: https://jsfiddle.net/avvac0x8/2/. But as you can see the viewport is not entirely in sync with the big image (and vice-versa).

It seems that the after mentioned solution does not work with different canvas sizes. So I need to make it "canvas-size agnostic".

Maybe I'm missing some kind of scalling calculations, but I don't know how to go from here, any tips are welcome.

EDIT:

Updated the fiddle to work properly: https://jsfiddle.net/avvac0x8/4/. Aparently the original image should not be scaled to fit the buffer canvas. What it should be done instead is that the offset/buffer canvas should be the same size as the original image.

Community
  • 1
  • 1
Thiago M
  • 181
  • 4
  • 23

1 Answers1

0

The simplest way is to use another canvas as a middle layer.

Here I will ignore the offset canvas because it is not needed unless you want to display the entire map. Presumably all you need is the zoomed in region. If you want to zoom out, you can simply draw the full image to your viewport window ( by providing the width and height parameters to ctx.drawImage ( img, x, y, viewPort.width, viewPort.height ) ). However you want to be sure that your image is manually cropped to an appropriate size so that the image does not appear stretched OR make sure that your canvas viewport is of the same aspect ratio as the image you are using.

The below works if you want the clipping region of the background ( the actual viewing area ) to be a different size (smaller or larger) than your viewport window ( the zoomed in/out viewing area ). Note that this is independent of how much larger or smaller the actual background is. Presumably both the clipped area and the viewport window are smaller than the background image itself.

For example:

// use these to determine where the clipping region lives
var offsetX = 0,
    offsetY = 0,
    clipWidth = <<yourChosenPixelWidth>>,
    clipHeight = <<yourChosenPixelHeight>>,
    clip = document.createElement ( "canvas" ),
    clipCtx,
    viewPort = document.getElementById ( "main-canvas" ),
    viewCtx = viewPort.getContext ( "2d" ),
    background = new Image (),
    // offsetCanvas = document.getElementById ( "offset-canvas" ),
    imgLoaded = false;

// configure the offset canvas once
background.src = "http://pixeljoint.com/files/icons/full/map__r1470206141.png";
background.onLoad = function() {
    // the fiddle scales the image, here we don't
    //offsetCanvas.width = background.width;
    //offsetCanvas.height = background.height;
    //offsetCtx = offsetCanvas.getContext ( "2d" );
    //offsetCtx.drawImage ( background, 0, 0 );
    imgLoaded = true;
}

clip.width = clipWidth;
clip.height = clipHeight;
clipCtx = clip.getContext ( "2d" );

function updateViewport () {
    if ( imgLoaded ) {
        // copy pixels from the background directly
        // to the middle layer so we have a "clipped"
        // but unscaled image object
        //clipCtx.putImageData ( offsetCtx.getImageData ( offsetX, offsetY, clip.width, clip.height ) );
        clipCtx.drawImage ( background, offsetX, offsetY );

        // this is where rescaling happens
        viewCtx.drawImage ( clip, 0, 0, viewPort.width, viewPort.height );

        // and you're done!
    }
}
Nolo
  • 846
  • 9
  • 19
  • I don't think I quite understood your explanation, maybe you could elaborate it on a fiddle? I'm using the following premises: - I need the buffered canvas to actually show where the current viewport is (that's exactly what your calling "middle layer") - If I understood what you just suggested is that I don't need to scale the original image to the canvas size. What I actually need is to have a canvas with the same width/height as the original image. Could you maybe elaborate it better updating my fiddle to work as you suggested as a proof of concept? – Thiago M Oct 16 '16 at 15:43
  • Even though I don't think that this answer is clear enough. I'm going to check it as the accepted one because it gave a rough idea of what was the real problem. I've edited the question so it includes a working example of the solution. A huge thanks! – Thiago M Oct 16 '16 at 17:04
  • @TMG sorry for the late reply. Glad I could help. :-) One of the things I wanted to point out though is that unless you need the offset canvas for a specific reason, the image object itself will have valid width and height properties after it loads and it can be drawn directly onto your main canvas. Either way it works the same. – Nolo Oct 16 '16 at 23:54
  • No problem at all. I'm glad your answer gave me the necessary input to work the solution out. Thanks ;) – Thiago M Oct 17 '16 at 00:08