pikaso icon indicating copy to clipboard operation
pikaso copied to clipboard

New center and zoom options

Open gniuslab opened this issue 2 years ago • 11 comments

Resume

I plan to change the way in which Pikaso creates the container, to give the possibility to center the image anything natively with an algorithm instead of using a css trick to center the background image to the stage, and additionally the function of zoomIn and zoomOut

Example of implementation to center any shape in stage for example in file Background.ts:125

if (options.size === 'resize') {
  this.board.centerAndFitShape(this.image.node)
}
/**
 * Centers the stage and fit especific shape
 * @param {Konva.Shape} shape
 * @param {number} padding = 2
 * @example
 * ```ts
 * editor.board.centerAndFitShape(Konva.Shape)
 * ```
 */
centerAndFitShape(shape, padding = 2) {
  padding = padding || 0

  const rawShapeRect = shape.getClientRect({ relativeTo: this.stage }), // Note: getClientRect gives size based on scaling - we want unscaled size so use 'relativeTo: stage' param to ensure consistent measurements.

    paddedShapeRect = {
      x: rawShapeRect.x - padding,
      y: rawShapeRect.y - padding,
      width: rawShapeRect.width + (2 * padding),
      height: rawShapeRect.height + (2 * padding)
    },

    viewRect = {
      width: Math.min(this.stage.width(), this.container.offsetWidth),
      height: Math.min(this.stage.height(), this.container.offsetHeight)
    },

    widthRatio = viewRect.width / paddedShapeRect.width, heightRatio = viewRect.height / paddedShapeRect.height,

    scale = widthRatio > heightRatio ? heightRatio : widthRatio,

    centeringAjustment = {
      x: (viewRect.width - paddedShapeRect.width * scale) / 2,
      y: (viewRect.height - paddedShapeRect.height * scale) / 2
    },

    finalPosition = {
      x: centeringAjustment.x + (-paddedShapeRect.x * scale),
      y: centeringAjustment.y + (-paddedShapeRect.y * scale)
    }

  this.stage.to({
    x: finalPosition.x,
    y: finalPosition.y,
    scaleX: scale,
    scaleY: scale,
    duration: 0.1
  })
}
/**
 * Zoom the stage
 * @param {number} scaleBy
 * @see zoomIn = 1.2, zoomOut = 0.8
 */
zoom (scaleBy) {
  const oldScale = this.stage.scaleX()

  // Calculate the center of the stage
  const pos = {
    x: this.stage.width() / 2,
    y: this.stage.height() / 2
  }

  // Calculate the position of the mouse relative to the stage
  const mousePointTo = {
    x: pos.x / oldScale - this.stage.x() / oldScale,
    y: pos.y / oldScale - this.stage.y() / oldScale
  }

  // Calculate the new scale of the stage
  const newScale = Math.max(0.05, oldScale * scaleBy)

  // Calculate the new position of the stage to keep it centered
  const newPos = {
    x: -(mousePointTo.x - pos.x / newScale) * newScale,
    y: -(mousePointTo.y - pos.y / newScale) * newScale
  }

  // Update the stage with the new position and scale
  this.stage.to({
    x: newPos.x,
    y: newPos.y,
    scaleX: newScale,
    scaleY: newScale,
    duration: 0.1
  })
}

gniuslab avatar Jan 05 '23 23:01 gniuslab

I am going to work on an algorithm that serves to pan and zoom with the mouse to integrate it if these recommendations are approved

gniuslab avatar Jan 06 '23 00:01 gniuslab

Hey @gniuslab It is fantastic that you are proposing adding Zooming to the core project, which is also being requested here: https://github.com/pikasojs/pikaso/issues/28

However, I am having difficulty understanding what the real use case is for centerAndFitShape. Let's talk about that in more detail.

raminious avatar Jan 06 '23 14:01 raminious

Hi @raminious , the use case of the centerAndFitShape function is quite trivial to make things uncenterable if they go off-canvas, you can see how it works in detail here...

https://codepen.io/JEE42/pen/ExEgeMX

Do this exercise, move one of the red squares out of your vision and click on Fit rect group to stage

gniuslab avatar Jan 07 '23 05:01 gniuslab

@gniuslab Is there any progress on the zooming feature?

raminious avatar Feb 03 '23 22:02 raminious

@raminious Yes, I can implement it, but I don't know the process of how to do it, in your main branch...

gniuslab avatar Feb 04 '23 05:02 gniuslab

think you ,You guys are the best.

ProjetNice avatar Jul 19 '23 13:07 ProjetNice

@raminious This only solves the scaling problem, but does not solve the box selection x y offset problem.Sorry, my English is not good IMG_2314

ProjetNice avatar Jul 19 '23 14:07 ProjetNice

This is my code

`stage.on('wheel', (e) => { e.evt.preventDefault(); let oldScale = stage.scaleX(); let scaleBy=1.1 let mousePointTo = { x: stage.getPointerPosition().x / oldScale - stage.x() / oldScale, y: stage.getPointerPosition().y / oldScale - stage.y() / oldScale, };

let newScale = e.evt.deltaY > 0 ? oldScale / scaleBy : oldScale * scaleBy ;
stage.scale({ x: newScale, y: newScale });


let newPos = {
  x: -(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale,
  y: -(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale,
};

stage.position(newPos);
stage.batchDraw();

});`

ProjetNice avatar Jul 19 '23 14:07 ProjetNice

@ProjetNice Use getRelativePointerPosition

gniuslab avatar Jul 19 '23 15:07 gniuslab

I still can't do this effect, I would like to add this feature and add it in the documentation, which is very helpful for beginners 1689832586468 -original-original

ProjetNice avatar Jul 20 '23 05:07 ProjetNice

I solved that problem, and this is my demo, using the vue framework

<template>
  <button @click="addPolygon">addPolygon</button>
  <button @click="changeDrag">changeDrag</button>

  <div id="editor" ref="root"></div>

</template>

<script setup>
import {ref, onMounted} from "vue";
import {Pikaso} from "pikaso";

let editor;
let stage;
let layer
let board
let selection

const root = ref(null);
let dragFlag=false

onMounted(() => {
  editor = new Pikaso({
    container: root.value,
    selection: {
      transformer: {
        borderStroke: "#1890ff",
        borderStrokeWidth: 0.5,
        anchorFill: "#1890ff",
        anchorSize: 10,
      },
      zone:{
        fill:"#1890ff50",
        stroke:'#1890ff',
      }
    },
  });
  editor.board.background.fill('rgba(0,0,0,0.5)')
  stage=editor.board.stage
  layer=editor.board.layer
  board=editor.board
  selection=board.selection
  
  /**
   * Used to calibrate mouse position
   */
  stage.on("mousemove touchmove",(e)=>{
    let zoom=selection.zone
    let x=zoom.attrs.x
    let y=zoom.attrs.y
    let width=zoom.attrs.width
    let height=zoom.attrs.height
    let sx=stage.x()
    let sy=stage.y()
    let scx=stage.scaleX()
    let scy=stage.scaleY()
    zoom.attrs.x = (x - sx) / scx;
    zoom.attrs.y = (y - sy) / scy;
    zoom.attrs.width=width/scx
    zoom.attrs.height=height/scy
  })
  /**
   * Resize the canvas
   */
  stage.on('wheel', (e) => {
    e.evt.preventDefault();
    let oldScale = stage.scaleX();
    let scaleBy=1.1
    let mousePointTo = {
      x: stage.getPointerPosition().x / oldScale - stage.x() / oldScale,
      y: stage.getPointerPosition().y / oldScale - stage.y() / oldScale,
    };

    let newScale = e.evt.deltaY > 0 ? oldScale / scaleBy : oldScale * scaleBy ;
    stage.scale({ x: newScale, y: newScale });


    let newPos = {
      x: -(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale,
      y: -(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale,
    };

    stage.position(newPos);
    stage.batchDraw();
  });

})
function addPolygon() {
  editor.shapes.polygon.insert({
    x: 250,
    y: 250,
    radius: 90,
    sides: 6,
    fill: "#fafafa",
  });
}
function changeDrag(){
  stage.draggable(!dragFlag)
  dragFlag=!dragFlag
}
</script>
<style>
#editor {
  width: 100%;
  height: 100vh;
  background: #ccc;
}
</style>

ProjetNice avatar Jul 20 '23 14:07 ProjetNice