Encountering a challenge with zooming in and out of an image displayed on canvas. The goal is to enable users to draw rectangles on the image, which is currently functioning well. However, implementing zoom functionality has presented the following issue:
When zooming in or out, the rectangles need to remain fixed relative to the image. While the rectangle stays in the same position, the part of the image contained within it changes as the image moves. How can I ensure that the rectangle covers the same part of the image even after zooming?
I am new to working with canvas and unsure about how to address this.
The problematic component is specified below:
tagger.component.html
<div class="row">
<div class="text-image-content">
<div class="column1">
<div style="position: relative;">
<div *ngFor="let drawItem of drawItems; let i = index">
<input [ngStyle]="{'left':drawItem.x0 + 'px' , 'top': drawItem.y0 + 'px'}"
style="position: absolute; z-index: 999;" placeholder="Enter Object Name"
[(ngModel)]="drawItem.name" type="text">
<input [ngStyle]="{'left':drawItem.x0 + 'px' , 'top': drawItem.y0 + 'px'}"
style="position: absolute; z-index: 999;" type="button" value="X" (click)="delete(i)">
</div>
<canvas #layer1 id="layer1" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<div class="row">
</div>
</div>
</div>
</div>
<button class="zoomBtn" (click)="zoomIn()">Zoom in</button>
<button class="zoomBtn" (click)="zoomOut()">Zoom out</button>
<div class="column2">
<div *ngFor="let drawItem of originalItems; let i = index">
<div class="details">
<div >Name: {{drawItem.name | titlecase}} </div>
<div>({{drawItem.x0}}, {{drawItem.y0}} , {{drawItem.x0 + drawItem.x1}}, {{drawItem.y0 + drawItem.y1}})</div>
</div>
</div>
</div>
</div>
tagger.component.ts
import {
Component,
ViewChild,
Input,
Output,
OnInit,
EventEmitter,
ElementRef
} from '@angular/core';
@Component({
selector: "app-tagger",
templateUrl: "./tagger.component.html",
styleUrls: ["./tagger.component.scss"]
})
export class TaggerComponent implements OnInit {
name = "Angular";
constructor() { }
drawItems = []
originalItems = []
count = 0
@Input('CanvasHeight') CanvasHeight
@Input('CanvasWidth') CanvasWidth
@Output() selected = new EventEmitter();
taggedItem = ""
showInput: boolean = false;
isMoving: boolean;
public imgWidth: number;
public uniX: number;
public uniY: number;
public uniX2: number;
public uniY2: number;
public initX: number;
public initY: number;
public imgHeight: number;
public url: string;
public image;
public originalImageWidth;
public originalImageHeight;
public hRatio;
public vRatio;
public translatePos = {x: this.CanvasWidth / 2, y: this.CanvasHeight / 2};
public scale = 1.0;
public scaleMultiplier = 0.8;
@ViewChild("layer1", { static: false }) layer1Canvas: ElementRef;
private context: CanvasRenderingContext2D;
private layer1CanvasElement: any;
ngOnInit(){
this.imageLoad();
}
delete(i) {
console.log(i)
this.drawItems.splice(i, 1);
this.originalItems.splice(i,1);
this.drawRect("red", 0, 0, 1);
}
imageLoad() {
this.image = new Image();
this.image.src = "https://i.ibb.co/12TJSNy/patio.jpg";
this.image.onload = () => {
console.log(this.CanvasWidth, this.CanvasHeight);
console.log(this.image.width, this.image.height);
this.originalImageWidth = this.image.width;
this.originalImageHeight = this.image.height;
this.image.width = this.CanvasWidth;
this.image.height = this.CanvasHeight;
this.hRatio = this.originalImageWidth/this.CanvasWidth;
this.vRatio = this.originalImageHeight/this.CanvasHeight;
this.layer1CanvasElement = this.layer1Canvas.nativeElement;
this.layer1CanvasElement.width = this.CanvasWidth;
this.layer1CanvasElement.height = this.CanvasHeight;
this.showImage();
}
}
showImage() {
this.count ++;
this.layer1CanvasElement = this.layer1Canvas.nativeElement;
this.context = this.layer1CanvasElement.getContext("2d");
this.context.clearRect(0, 0, this.CanvasWidth, this.CanvasHeight);
this.context.save();
this.context.translate(this.translatePos.x, this.translatePos.y);
this.context.scale(this.scale, this.scale);
this.context.drawImage(this.image, 0,0, this.image.width, this.image.height);
this.context.restore()
let parent = this;
if(this.count==1){
this.layer1CanvasElement.addEventListener("mousedown", (e) => {
this.isMoving = true
this.initX = e.offsetX;
this.initY = e.offsetY;
});
this.layer1CanvasElement.addEventListener("mouseup", (e) => {
this.isMoving = false
this.showInput = true
this.drawItems.push({
name: "",
x0: this.initX,
y0: this.initY,
x1: this.uniX,
y1: this.uniY
});
this.originalItems.push({
name: "",
x0: Math.ceil(this.initX * this.hRatio),
y0: Math.ceil(this.initY * this.vRatio),
x1: Math.ceil(this.uniX *this.hRatio),
y1: Math.ceil(this.uniY * this.vRatio)
});
parent.drawRect("red", e.offsetX - this.initX, e.offsetY - this.initY, 0);
this.uniX = undefined
this.uniY = undefined
});
}
this.layer1CanvasElement.addEventListener("mousemove", (e) => {
if (this.isMoving) {
parent.drawRect("red", e.offsetX - this.initX, e.offsetY - this.initY, 0);
}
});
this.drawRect("red", 0, 0, 1);
}
drawRect(color = "black", height, width, flag) {
if (this.uniX | flag) {
this.context.clearRect(0, 0, this.CanvasWidth, this.CanvasHeight);
this.context.save();
this.context.translate(this.translatePos.x, this.translatePos.y);
this.context.scale(this.scale, this.scale);
this.context.drawImage(this.image, 0,0, this.image.width, this.image.height);
this.context.restore()
}
// console.log(this.image.width, this.image.height)
this.uniX = height
this.uniY = width
this.uniX2 = height
this.uniY2 = width
for (var i = 0; i < this.drawItems.length; i++) {
this.context.beginPath();
this.context.rect(
this.drawItems[i].x0,
this.drawItems[i].y0,
this.drawItems[i].x1,
this.drawItems[i].y1
);
this.context.lineWidth = 3;
this.context.strokeStyle = color;
this.context.stroke();
}
}
zoomIn(){
console.log("zooming in")
this.scale /= this.scaleMultiplier;
this.showImage();
}
zoomOut(){
console.log("zooming out")
this.scale *= this.scaleMultiplier;
this.showImage();
}
}
tagger.component.scsss
.button {
color: transparent;
cursor: pointer;
}
.column1 {
float: left;
width: 70%;
padding: 10px;
height: 300px; /* Should be removed. Only for demonstration */
}
.column2 {
float: left;
width: 25%;
padding: 10px;
height: 300px; /* Should be removed. Only for demonstration */
}
/* Clear floats after the columns */
.row:after {
content: "";
display: table;
clear: both;
}
.details {
margin: 10px;
}
.text-image-content{
border: 1px solid #979797;
border-radius: 8px;
padding: 0 10px;
margin-left:20px;
margin-right:20px;
height: 300px;
overflow-y: scroll;
overflow-x: scroll;
scrollbar-width: thin;
}
.zoomBtn{
// height:10px;
}
Access the complete code at : https://stackblitz.com/edit/angular-tsotfa?file=src%2Fapp%2Ftagger%2Ftagger.component.ts
Thank you!