Javascript 缩放、旋转和平移后,画布绘制点定位到单击区域
我有一个有点复杂的问题(我做了很多研究,但没有找到我要找的东西),基本上我正在构建一个标签工具,在那里我可以得到一组图像,我希望能够单击对象的角,并创建一个用户单击的点 需要注意的几件事(我已经做了这些)Javascript 缩放、旋转和平移后,画布绘制点定位到单击区域,javascript,reactjs,typescript,canvas,Javascript,Reactjs,Typescript,Canvas,我有一个有点复杂的问题(我做了很多研究,但没有找到我要找的东西),基本上我正在构建一个标签工具,在那里我可以得到一组图像,我希望能够单击对象的角,并创建一个用户单击的点 需要注意的几件事(我已经做了这些) 图像可以是任何方向,我需要旋转它们(从方向旋转) 图像开始时应按比例缩小以适应画布(将比例设置为“缩小”图像和画布大小) 用户可以“平移”(根据箭头键进行翻译) 用户可以放大和缩小图像(使用shift+上/下箭头缩放) 用户可以将图像重置回中心(空格键居中,shift+空格键重置缩放初始和重
- 图像可以是任何方向,我需要旋转它们(从方向旋转)
- 图像开始时应按比例缩小以适应画布(将比例设置为“缩小”图像和画布大小)
- 用户可以“平移”(根据箭头键进行翻译)
- 用户可以放大和缩小图像(使用shift+上/下箭头缩放)
- 用户可以将图像重置回中心(空格键居中,shift+空格键重置缩放初始和重新居中)
为了给出一些真实的背景,我制作了一个代码笔来显示发生了什么
const red='#ff0000';
类应用程序扩展了React.Component{
私有画布:HTMLCanvasElement
私有映像=新映像
私有ctx:CanvasRenderingContext2D |空
私人资料:任何
私人定向:编号=270
私有移动键:{[key:number]:number}={}
私人cw:号码
私人ch:号码
私有scaleFactor:number=1.00
私人startX:号码
私人明星:号码
专用窗格:编号
私人公司:号码
私有:布尔值
私有defaultScaleFactor:number=1.00
专用图像点:编号[][]=[]
loadImage=(url:string)=>{
this.image.onload=()=>{
const iw=this.orientation==0 | | this.orientation==180?this.image.width:this.image.height
const ih=this.orientation==0 | | this.orientation==180?this.image.height:this.image.width
const size=Math.min(this.canvas.width/iw,this.canvas.height/ih)
this.defaultScaleFactor=更小
this.scaleFactor=更小
}
this.image.src=https://i.stack.imgur.com/EYvnM.jpg'
}
组件将卸载(){
document.removeEventListener('keyup',this.handleKeyUp)
document.removeEventListener('keydown',this.handleKeyDown)
//window.removeEventListener('resize',this.resizeCanvas)
this.canvas.removeEventListener('click',this.handleCanvasClick)
}
componentDidMount(){
this.isshift=false
document.addEventListener('keyup',this.handleKeyUp)
document.addEventListener('keydown',this.handleKeyDown)
//window.addEventListener('resize',this.resizeCanvas)//本例不需要
requestAnimationFrame(this.animate)
const elem=document.getElementById('canvasContainer')
如果(!elem)返回
const rect=elem.getBoundingClientRect()
this.canvas.addEventListener('click',this.handleCanvasClick)
this.canvas.width=rect.width
this.canvas.height=rect.height
this.ctx=this.canvas.getContext('2d')
this.cw=this.canvas.width
this.ch=this.canvas.height
this.startX=-(this.cw/2)
this.startY=-(this.ch/2)
this.panX=this.startX
this.panY=this.startY
这是loadImage()
}
handleCanvasClick=(e)=>{
让rect=this.canvas.getBoundingClientRect()
设x=e.clientX-rect.left
设y=e.clientY-rect.top
this.imagePoints.push([x,y])
}
动画=()=>{
Object.keys(this.moveKeys).forEach(key=>{
this.handleMovement(键,this.moveKeys[键])
})
这个
requestAnimationFrame(this.animate)
}
手部移动=(键、数量)=>{
常数移动单位=20
开关(parseInt(键)){
案例32://空格键
this.panX=this.startX
this.panY=this.startY
如果(按下此按钮){
this.scaleFactor=this.defaultScaleFactor
}
打破
案例37://左
如果(this.orientation==90 | | this.orientation==270){
this.panY-=移动单位
}否则{
this.panX-=moveUnit
}
打破
案例38://以上
如果(按下此按钮){
this.scaleFactor*=1.1
}否则{
如果(this.orientation==90 | | this.orientation==270){
this.panX+=moveUnit
}否则{
this.panY+=移动单位
}
}
打破
案例39://对
如果(this.orientation==90 | | this.orientation==270){
this.panY+=移动单位
}否则{
this.panX+=moveUnit
}
打破
案例40://向下
如果(按下此按钮){
this.scaleFactor/=1.1
}否则{
如果(this.orientation==90 | | this.orientation==270){
this.panX-=moveUnit
}否则{
this.panY-=移动单位
}
}
打破
违约:
打破
}
}
handleKeyUp=(e)=>{
if(e.shiftKey | | e.keyCode==16){
this.isshift=false
}
删除此项。移动键[e.keyCode]
}
handleKeyDown=(e)=>{
e、 预防默认值()
if(e.shiftKey | | e.keyCode==16){
this.isshift=true
}
e、 this.moveKeys中的keyCode?this.moveKeys[e.keyCode]+=1:this.moveKeys[e.keyCode]=1
}
drawTranslated=()=>{
如果(!this.ctx)返回
const ctx=this.ctx
clearRect(0,0,this.cw,this.ch)
ctx.save()
ctx.translate(this.cw/2,this.ch/2)
ctx.r
const red = '#ff0000';
class App extends React.Component<{}, {}> {
private canvas: HTMLCanvasElement
private image = new Image
private ctx: CanvasRenderingContext2D | null
private data: any
private orientation: number = 270
private moveKeys: {[key: number]: number} = {}
private cw: number
private ch: number
private scaleFactor: number = 1.00
private startX: number
private startY: number
private panX: number
private panY: number
private isShiftPressed: boolean
private defaultScaleFactor: number = 1.00
private imagePoints: number[][] = []
loadImage = (url: string) => {
this.image.onload = () => {
const iw = this.orientation === 0 || this.orientation === 180 ? this.image.width : this.image.height
const ih = this.orientation === 0 || this.orientation === 180 ? this.image.height : this.image.width
const smaller = Math.min(this.canvas.width / iw, this.canvas.height / ih)
this.defaultScaleFactor = smaller
this.scaleFactor = smaller
}
this.image.src = 'https://i.stack.imgur.com/EYvnM.jpg'
}
componentWillUnmount() {
document.removeEventListener('keyup', this.handleKeyUp)
document.removeEventListener('keydown', this.handleKeyDown)
// window.removeEventListener('resize', this.resizeCanvas)
this.canvas.removeEventListener('click', this.handleCanvasClick)
}
componentDidMount() {
this.isShiftPressed = false
document.addEventListener('keyup', this.handleKeyUp)
document.addEventListener('keydown', this.handleKeyDown)
// window.addEventListener('resize', this.resizeCanvas) // dont need for this example
requestAnimationFrame(this.animate)
const elem = document.getElementById('canvasContainer')
if (!elem) return
const rect = elem.getBoundingClientRect()
this.canvas.addEventListener('click', this.handleCanvasClick)
this.canvas.width = rect.width
this.canvas.height = rect.height
this.ctx = this.canvas.getContext('2d')
this.cw = this.canvas.width
this.ch = this.canvas.height
this.startX = -(this.cw / 2)
this.startY = -(this.ch / 2)
this.panX = this.startX
this.panY = this.startY
this.loadImage()
}
handleCanvasClick = (e) => {
let rect = this.canvas.getBoundingClientRect()
let x = e.clientX - rect.left
let y = e.clientY - rect.top
this.imagePoints.push([x, y])
}
animate = () => {
Object.keys(this.moveKeys).forEach( key => {
this.handleMovement(key, this.moveKeys[key])
})
this.drawTranslated()
requestAnimationFrame(this.animate)
}
handleMovement = (key, quantity) => {
const moveUnit = 20
switch (parseInt(key)) {
case 32: // spacebar
this.panX = this.startX
this.panY = this.startY
if (this.isShiftPressed) {
this.scaleFactor = this.defaultScaleFactor
}
break
case 37: // left
if (this.orientation === 90 || this.orientation === 270) {
this.panY -= moveUnit
} else {
this.panX -= moveUnit
}
break
case 38: // up
if (this.isShiftPressed) {
this.scaleFactor *= 1.1
} else {
if (this.orientation === 90 || this.orientation === 270) {
this.panX += moveUnit
} else {
this.panY += moveUnit
}
}
break
case 39: // right
if (this.orientation === 90 || this.orientation === 270) {
this.panY += moveUnit
} else {
this.panX += moveUnit
}
break
case 40: // down
if (this.isShiftPressed) {
this.scaleFactor /= 1.1
} else {
if (this.orientation === 90 || this.orientation === 270) {
this.panX -= moveUnit
} else {
this.panY -= moveUnit
}
}
break
default:
break
}
}
handleKeyUp = (e) => {
if (e.shiftKey || e.keyCode === 16) {
this.isShiftPressed = false
}
delete this.moveKeys[e.keyCode]
}
handleKeyDown = (e) => {
e.preventDefault()
if (e.shiftKey || e.keyCode === 16) {
this.isShiftPressed = true
}
e.keyCode in this.moveKeys ? this.moveKeys[e.keyCode] += 1 : this.moveKeys[e.keyCode] = 1
}
drawTranslated = () => {
if (!this.ctx) return
const ctx = this.ctx
ctx.clearRect(0, 0, this.cw, this.ch)
ctx.save()
ctx.translate(this.cw / 2, this.ch / 2)
ctx.rotate(this.orientation * Math.PI / 180)
ctx.scale(this.scaleFactor, this.scaleFactor)
ctx.translate(this.panX, this.panY)
const transformedWidth = this.canvas.width / 2 - this.image.width / 2
const transformedHeight = this.canvas.height / 2 - this.image.height / 2
ctx.drawImage(
this.image,
transformedWidth,
transformedHeight
)
const pointSize = 10
if (this.imagePoints.length > 0) {
this.imagePoints.forEach( ([x, y]) => {
ctx.fillStyle = red
ctx.beginPath()
// Obviously the x and y here need to be transformed to work with the current scale, rotation and translation. But I'm stuck here!
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true)
ctx.closePath()
ctx.fill()
})
}
ctx.restore()
}
handleResetUserClicks = () => {
this.imagePoints = []
}
render() {
return (
<div id="container">
<div>Use arrow keys to pan the canvas, shift + up / down to zoom, spacebar to reset</div>
<div id="canvasContainer">
<canvas ref={this.assignCameraRef} id="canvasElement" style={{ position: 'absolute' }} ref={this.assignCameraRef} />
</div>
<div>
<button onClick={this.handleResetUserClicks}>Reset Clicks</button>
</div>
</div>
)
}
assignCameraRef = (canvas: HTMLCanvasElement) => this.canvas = canvas
}
// NOTE rotate is in radians
// with panX, and panY added
var matrix = [1,0,0,1,0,0];
var invMatrix = [1,0,0,1];
function createMatrix(x, y, scale, rotate, panX, panY){
var m = matrix; // just to make it easier to type and read
var im = invMatrix; // just to make it easier to type and read
// create the rotation and scale parts of the matrix
m[3] = m[0] = Math.cos(rotate) * scale;
m[2] = -(m[1] = Math.sin(rotate) * scale);
// add the translation
m[4] = x;
m[5] = y;
// transform pan and add to the position part of the matrix
m[4] += panX * m[0] + panY * m[2];
m[5] += panX * m[1] + panY * m[3];
//=====================================
// calculate the inverse transformation
// first get the cross product of x axis and y axis
cross = m[0] * m[3] - m[1] * m[2];
// now get the inverted axis
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
}