Scaling IMAGE around pointer in Konva - konvajs

Can't wrap my head around this and get it to work. Trying to transpose this sample on Konva, but can not get it to work with an image inside a layer, inside the stage.
The sample I am trying to reproduce is the "Zooming stage relative to pointer position" sample.
https://konvajs.org/docs/sandbox/Zooming_Relative_To_Pointer.html
Any help would generate kudos.

The trick with zooming from an arbitrary point such as the mouse position is to understand that you must relocate the stage to keep the point under the mouse consistent. I provide the working vanilla JS snippet below to illustrate the point. The guts are based on the original demo at the Konva docs site, just with more comments in the code. Translating into React should not be difficult once the basis of the technique is explained.
The gray area is the canvas, and the pink rect is the stage. Note that we start with the stage positioned into the canvas just to show that the stage can be moved like any other object.
The stage has a dot-pattern to help show the effects in the demo. The solution works for any layout of layers, shapes or images (as per OP question). Note that the shapes themselves do not need to be handled individually - the scaling is applied to the stage.
Move your mouse anywhere on the stage and scroll to see the zoom effect, noting how the top-left of the pink stage rectangle moves with the zoom. Now un-tick the checkbox to turn off the stage position adjustment - note now that the zoom is no longer respecting the mouse position because the stage position is not being moved.
Thus we have illustrated that it is necessary to move the stage sympathetically during the zoom but how do we know the amount to move by?
The tip to know is that you need to get the absolute distance of the mouse position over the stage before the new scale is applied, then apply the new scale to the stage, then use the computed distance * new scale to compute the new stage position. See the stage.on('wheel') event in the snippet for working code.
See also the codepen here.
// Making the stage
let stage = new Konva.Stage({
container: "container1",
width: $('#container1').width(),
height: $('#container1').height(),
draggable: true,
x: 40,
y: 60
});
// Make the layer
let layer = new Konva.Layer();
// make a rect to fill stage to show where it is.
let rect = new Konva.Rect({
x:0,
y: 0,
width: $('#container1').width(),
height: $('#container1').height(),
fill: 'magenta',
opacity: 0.3
})
layer.add(rect);
let grid = {w: $('#container1').width(), h: $('#container1').height()},
gridStep = 40,
mouseCircleRadius = 80,
circles = [],
dotRadius = 10;
// Make the grid of circles
for (let i = gridStep; i < grid.w; i = i + gridStep ){
for (let j = gridStep; j < grid.h; j = j + gridStep ){
let c = new Konva.Circle({ x: i, y: j, radius: dotRadius, fill: 'cyan'})
circles.push(c);
layer.add(c)
}
}
// Add layer to stage
stage.add(layer)
stage.on('mousemove', function (e) {
var pointer = stage.getPointerPosition();
// show the pointer and stage positions for illustration
$('#trace').html('Pointer.x = ' + pointer.x + ' stage.x() = ' + stage.x())
});
// this is the scale factor to be applied at each step.
var scaleBy = 1.01;
// this is the event that fires when the mouse wheel spins.
stage.on('wheel', (e) => {
e.evt.preventDefault();
// note the old scale to be used when deciding the current stage position
var oldScale = stage.scaleX();
// note the mouse pointer position relative to the stage at current scale
var pointer = stage.getPointerPosition();
// calculate the mouse pointer position at scale = 1.
// pointer.x is relative to the canvas - not affected by stage scale,
// stage.x() is the poistion of the stage on the canvas.
// This gives the distance from the pointer to the
var mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
// Calculate the new scale - slightly different calc for zoom in versus zoom out, as you would expect.
var newScale = (e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy);
// Apply the new scale to the stage. Note that, assuming a zoom-in op, at this point in time the shapes on the stage would
// seem to have jumped down + right. We will shortly 'move' the stage left+up to counter this effect.
stage.scale({ x: newScale, y: newScale });
// To do that we have to calculate what the position of the stage must be relative to the mouse pointer position at the new scale.
// Note - remove the 'if' for your live code, the checkbox is for illustration only.
if ($('#fixstagepos').prop('checked')){
var newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
// and apply the new position to the stage. Again in the case of a zoom-in op this has the effect of moving the stage left + up, countering the apparent movement
// caused by the change in scale.
stage.position(newPos);
}
});
.container {
width: 800px;
height: 400px;
background-color: silver;
margin: 5px;
}
#trace {
max-height: 200px;
overflow-y: scroll;
font-family: 'Courier'
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<p id="info">Grey is the canvas and the pink rect shows the stage position. The stage is intitially positioned at {x: 40, y: 60} and can be dragged.</p>
<p>Move mouse over stage and scroll to zoom in & out. Use checkbox to see what happens without stage pos correction. </p>
<p>Apply correction to stage position <input id='fixstagepos' type='checkbox' checked='1' /></p>
<div id='container1' class='container'>
</div>
<p id='trace'></p>

Related

How to get clicked element's position and id to change color and draw lines on it

I'm developing a basic window frame configurator. I splitted glasses in function below. I want to change color when i clicked and get the position of clicked glass to draw openin direction lines.
I tried to understand and implement Lavrton's method(https://codesandbox.io/s/0xj7zml2zl?file=/src/index.js) but i couldn't succeed.
function glassDraw(piece, frameWidth, frameHeight) {
var glassGroup = new Konva.Group();
for (i = 0; i <piece; i++) {
var glass = new Konva.Rect({
name: 'Rect',
x: padding + (frameWidth / piece) * i,
y: padding,
width: frameWidth / piece,
height: frameHeight - padding * 2,
fill: 'lightblue',
id: 'glass'+i,
});
glassGroup.add(glass);
}
glassGroup.find("Rect").on('click', function (e) {
// get id of the cube i drag
var clickedId = e.target.attrs.id;
$('#theId').html(clickedId);
})
return glassGroup;
}
When i use getelementbyid method with id of konva element("glass"+i), it returns null. I think i need to get as a konva element.
You have to create a click listener for all of your rectangles.
for (let rect of glassGroup.children) {
rect.on('click', () => {
console.log(rect.x(), rect.y()); // current position of the object
console.log(rect.id()); // log id of the object
rect.fill('green'); // set color to green
layer.batchDraw(); // update layer (batchDraw just for better performance .draw() would work to)
})
}
Make sure you always update the stage by either call stage.draw() or layer.draw() (or batchDraw() for better performance) after changing something, otherwise your stage will not show anything of what you do.
If something with this code don't work feel free to ask.
Have a nice day.

Konva - visualize custom hit area

Is there an easy way to visualize a custom hit area shape?
As described here
https://konvajs.github.io/docs/events/Custom_Hit_Region.html
the hitFunc attribute can be set to a function that uses the supplied context to draw a custom hit area / region. Something like this:
var star = new Konva.Star({
...
hitFunc: function (context) {
context.beginPath()
context.arc(0, 0, this.getOuterRadius() + 10, 0, Math.PI * 2, true)
context.closePath()
context.fillStrokeShape(this)
}
})
For debugging purposes, I would like an easy way to toggle visual rendering of the shape (circle in this case), eg by filling it yellow.
Thanks :)
Currently, there is no public API for that. But you still can add hit canvas into the page somewhere and see how it looks:
const hitCanvas = layer.hitCanvas._canvas;
document.body.appendChild(hitCanvas);
// disable absolute position:
hitCanvas.style.position = '';
http://jsbin.com/mofocagupi/1/edit?js,output
Or you can add hit canvas on top of the stage and apply an opacity to make scene canvases visible:
const hitCanvas = layer.hitCanvas._canvas;
stage.getContainer().appendChild(hitCanvas);
hitCanvas.style.opacity = 0.5;
hitCanvas.style.pointerEvents = 'none';
http://jsbin.com/gelayolila/1/edit?js,output

Canvas zoomIn/zoomOut: how to avoid loss of image quality?

I have this code that I used for scaling images. To zoomIn and zoomOut is use the code scalePicture(1.10, drawingContext); and scalePicture(0.90, drawingContext);. I perform that operations on a off screen canvas and then copy the image back to the original screen.
I make use of the offscreen processing since the browser optimizes the image operations by using double buffering. I am still having the issue that when I zoomIn by around 400% and then zoomOut back to the original size, there is a significant loss of image quality.
I am not depending on the original image because the user can perform many operations such as clip, crop, rotate, annotate and I need to stack all the operations on the original image.
Can anyone throw some advice/suggestions around any means to preserve the quality of the image while not sacrificing the performance and quality.
scalePicture : function(scalePercent, operatingCanvasContext) {
var w = operatingCanvasContext.canvas.width,
h = operatingCanvasContext.canvas.height,
sw = w * scalePercent,
sh = h * scalePercent,
operatingCanvas = operatingCanvasContext.canvas;
var canvasPic = new Image();
operatingCanvasContext.save();
canvasPic.src = operatingCanvas.toDataURL();
operatingCanvasContext.clearRect (0,0, operatingCanvas.width, operatingCanvas.height);
operatingCanvasContext.translate(operatingCanvas.width/2, operatingCanvas.height/2);
canvasPic.onload = function () {
operatingCanvasContext.drawImage(canvasPic, -sw/2 , -sh/2 , sw, sh);
operatingCanvasContext.translate(-operatingCanvas.width/2, -operatingCanvas.height/2);
operatingCanvasContext.restore();
};
}
Canvas is draw and forget. There is no way to preserve original quality without referencing the original source.
I would suggest to reconstruct the recorded stack but using a transformation matrix for the changes in scale, rotation etc. Then apply the accumulated matrix on the original image. This will preserve the optimal quality as well as provide some gain in performance (as you only draw the last and current state).
Similar for clipping, calculate and merge the clipping regions using the same matrix and apply clip before drawing in the original image in the final step. And similar with text etc.
It's a bit too broad to show an example that does all these steps, but here is an example showing how to use accumulated matrix transforms on the original image preserving optimal quality. You can see that you can zoom in and out, rotate and the image will in each instance render at optimal quality.
Example of Concept
var ctx = c.getContext("2d"), img = new Image; // these lines just for demo init.
img.onload = demo;
ctx.fillText("Loading image...", 20, 20);
ctx.globalCompositeOperation = "copy";
img.src = "http://i.imgur.com/sPrSId0.jpg";
function demo() {
render();
zin.onclick = zoomIn; // accumulates transform, but render
zout.onclick = zoomOut; // based on original image using.
zrot.onclick = rotate; // current transformation matrix
}
function render() {ctx.drawImage(img, 0, 0)} // render original image
function zoomIn() {
ctx.translate(c.width * 0.5, c.height * 0.5); // pivot = center
ctx.scale(1.05, 1.05);
ctx.translate(-c.width * 0.5, -c.height * 0.5);
render();
}
function zoomOut() {
ctx.translate(c.width * 0.5, c.height * 0.5);
ctx.scale(1/1.05, 1/1.05);
ctx.translate(-c.width * 0.5, -c.height * 0.5);
render();
}
function rotate() {
ctx.translate(c.width * 0.5, c.height * 0.5);
ctx.rotate(0.3);
ctx.translate(-c.width * 0.5, -c.height * 0.5);
render();
}
<button id=zin>Zoom in</button>
<button id=zout>Zoom out</button>
<button id=zrot>Rotate</button><br>
<canvas id=c width=640 height=378></canvas>

Cocos2d JS HTML5

I want to rotate this image in cocos2d Java-script HTML5.
Upper and lower circles are two different sprites.
I am using this code:
var RotationAmount=0;
Top=cc.Sprite.create("Assets/Top.png");
Top.setPosition(MidX,MidY+100);
Top.schedule(function(){
if(RotationAmount>360)
RotationAmount=0;
});
this.addChild(Top);
Here's how my sprites look:
The above this causes my sprite starts to rotate around itself. I want to make it rotate around a point, so where am I going wrong?
You can do next:
var Top = cc.Sprite.create("Assets/Top.png");
var rotateAction = cc.RotateBy.create(0, 1);
Top.attr({
x: MidX,
y: MidY,
anchorX: 0.5,
anchorY: 0.5
});
Top.schedule(function() {
Top.runAction(rotateAction);
});
this.addChild(Top);

openlayers-3 precompose, how to do layer clipping with a rectangular geometry

I'm trying to make a simple app doing something similar to the Layer Spy Example, but instead of a circle flowing the mouse pointer, I would like to do the clipping based on a rectangle which is always centered in the map.
(preferably with "fixed" size" i.e. changes "extent" when zooming)
Any help appreciated
Thanks
Frode
You can do it like in the example that you are referring to. But instead of context.arc() use context.rect() with a fixed position to clip the layer. Something like:
imagery.on('precompose', function(event) {
var ctx = event.context;
var pixelRatio = event.frameState.pixelRatio;
ctx.save();
ctx.beginPath();
var x = ctx.canvas.width / 2 - 100;
var y = ctx.canvas.height / 2 - 100;
ctx.rect(x, y, 100, 100);
ctx.clip();
});
http://jsfiddle.net/eo1c1x78/

Resources