I'm having an issue with Ionic 2 (beta.7) and Paper JS: a white box appears at the top left corner of the canvas when I start drawing. See an example picture here.The canvas also flickers when drawing.
A short description as to what I'm trying to do: Take a picture with the device camera and draw that image on a canvas and then allow the user to draw on the image.
Any ideas what I'm doing wrong? Or suggestions on how to fix this?
My code:
Taking the picture (using ionic native):
public takePicture(): void {
// Options for the camera plugin
let options = {
quality: 20,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: 1,
encodingType: Camera.EncodingType.JPEG,
mediaType: Camera.MediaType.PICTURE,
correctOrientation: true
};
Camera.getPicture(options).then((imageData) => {
this.nav.push(DrawPicturePage, { imagePath: imageData });
}, (err) => {
});
}
DrawPicturePage (pictureDraw.ts):
import {Platform, Page, NavController, NavParams} from 'ionic-angular';
import {ElementRef, ViewChild, NgZone} from '@angular/core';
import * as paper from 'paper';
@Page({
templateUrl: 'build/pages/pictureDraw/pictureDraw.html',
})
export class DrawPicturePage {
@ViewChild("theCanvas") theCanvas: ElementRef;
@ViewChild("content") content: ElementRef;
private path;
private ctx: CanvasRenderingContext2D; // canvas context
private imagePath: string = '';
private canvasWidth: number = 100;
private canvasHeight: number = 100;
private canvasInnerWidth: number = 0;
private canvasInnerHeight: number = 0;
private width: string = '0%';
private height: string = '0%';
constructor(
public navParams: NavParams,
private zone: NgZone) {
this.imagePath = navParams.get('imagePath');
}
public ngAfterViewInit(): void {
this.ctx = this.theCanvas.nativeElement.getContext("2d");
this.loadImage();
}
private loadImage(): void {
let image = new Image();
image.src = this.imagePath;
image.onload = () => {
// count the image ratio
let ratio: number = image.width / image.height;
// create a canvas and a context for image resizing
let oc = document.createElement('canvas');
let octx = oc.getContext('2d');
oc.width = image.width;
oc.height = image.height;
// resize to max size 1600x900
if (image.width > image.height && image.width > 1600) {
oc.width = 1600;
oc.height = 1600 / ratio;
if (oc.height > 900) {
oc.height = 900;
oc.width = 900 * ratio;
}
} else if (image.height > 1600) {
oc.height = 1600;
oc.width = 1600 * ratio;
if (oc.width > 900) {
oc.width = 900;
oc.height = 900 / ratio;
}
}
octx.drawImage(image, 0, 0, oc.width, oc.height);
// calculate the UI size of the canvas
if (image.width > image.height) {
// landscape
this.canvasWidth = this.content.nativeElement.offsetWidth;
this.canvasHeight = this.canvasWidth / ratio;
} else {
// portrait
this.canvasHeight = this.content.nativeElement.offsetHeight;
this.canvasWidth = this.canvasHeight * ratio;
}
// set the canvas UI width and height
this.zone.run(() => {
this.width = Math.round(this.canvasWidth) + 'px';
this.height = Math.round(this.canvasHeight) + 'px';
});
// set the canvas inner width and height
this.canvasInnerWidth = oc.width;
this.canvasInnerHeight = oc.height;
this.setupPaperJS(oc);
};
}
/** setup paper js */
private setupPaperJS(canvas: any = null): void {
paper.install(window);
paper.setup(this.theCanvas.nativeElement);
this.theCanvas.nativeElement.width = this.canvasInnerWidth;
this.theCanvas.nativeElement.height = this.canvasInnerHeight;
// draw the image
this.ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height,
0, 0, this.theCanvas.nativeElement.width, this.theCanvas.nativeElement.height);
}
private downEvent(event: TouchEvent): void {
console.info('downEvent');
let touch: { x: number, y: number } = this.touchCoordToCanvasCoord(event.touches[0].screenX, event.touches[0].screenY);
let point: paper.Point = new paper.Point();
point.x = touch.x;
point.y = touch.y;
if (this.path) {
this.path.selected = false;
}
// Create a new path
this.path = new paper.Path({
segments: [point],
strokeColor: 'red',
strokeWidth: 10
});
}
private dragEvent(event: TouchEvent): void {
console.info('dragEvent');
let touch: { x: number, y: number } = this.touchCoordToCanvasCoord(event.touches[0].screenX, event.touches[0].screenY);
let point: paper.Point = new paper.Point();
point.x = touch.x;
point.y = touch.y;
this.path.lineTo(point);
}
private upEvent(event: TouchEvent): void {
console.info('upEvent');
this.path.simplify(10);
}
/** Converts the touch coordinate to canvas coordinates */
private touchCoordToCanvasCoord(touchX: number, touchY: number): { x: number, y: number } {
let rv: { x: number, y: number } = { x: 0, y: 0 };
let x0: number = 0, y0: number = 0;
x0 = this.theCanvas.nativeElement.getBoundingClientRect().left;
y0 = this.theCanvas.nativeElement.getBoundingClientRect().top + 45;
rv.x = this.canvasInnerWidth / (window.screen.width - 2 * x0) * (touchX - x0);
rv.y = this.canvasInnerHeight / (window.screen.height - 2 * y0) * (touchY - y0);
if (rv.x < 0) rv.x = 0;
if (rv.y < 0) rv.y = 0
if (rv.x > this.canvasInnerWidth) rv.x = this.canvasInnerWidth;
if (rv.y > this.canvasInnerHeight) rv.y = this.canvasInnerHeight;
return rv;
}
/*------------------------------- functions called from template -------------------------------*/
private getWidth(): string {
return this.width;
}
private getHeight(): string {
return this.height;
}
}
The template:
<ion-navbar primary *navbar>
<ion-title>My title</ion-title>
</ion-navbar>
<ion-content>
<div #content style="width: 100%; height: 100%;">
<canvas #theCanvas class="canvasStyle" [style.width]="getWidth()" [style.height]="getHeight()" (touchstart)="downEvent($event)"
(touchend)="upEvent($event)" (touchmove)="dragEvent($event)"></canvas>
</div>
</ion-content>
And the "canvasStyle" class:
.canvasStyle{
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
position: absolute;
background-color: rgba(0,0,0,0);
-webkit-tap-highlight-color:rgba(0,0,0,0);
}
Any feedback would be much appreciated.
The drawing wasn't working correctly because my canvas wasn't set up right for paperjs. The "flickering" seemed to be caused by the large size of the canvas.
What I ended up doing was drawing my image on one canvas and then using paperjs on another, smaller, canvas. Then before saving the final image I drew the paper canvas on top of the image canvas.
So in html I have:
<canvas #imageCanvas class="canvasStyle" [style.width]="getWidth()" [style.height]="getHeight()"></canvas>
<canvas #paperCanvas class="canvasStyle" [style.width]="getWidth()" [style.height]="getHeight()" (touchstart)="downEvent($event)"
(touchend)="upEvent($event)" (touchmove)="dragEvent($event)"></canvas>
And in my .ts:
@ViewChild("imageCanvas") imageCanvas: ElementRef;
@ViewChild("paperCanvas") paperCanvas: ElementRef;
...
public ngAfterViewInit(): void {
this.ctx = this.imageCanvas.nativeElement.getContext('2d');
}
...
private setupPaperJS(canvas): void {
paper.install(window);
paper.setup(this.paperCanvas.nativeElement);
this.imageCanvas.nativeElement.width = this.canvasInnerWidth;
this.imageCanvas.nativeElement.height = this.canvasInnerHeight;
// draw the image
this.ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height,
0, 0, this.imageCanvas.nativeElement.width, this.imageCanvas.nativeElement.height);
}
...
private combineCanvases() {
this.ctx.drawImage(this.paperCanvas.nativeElement, 0, 0, this.paperCanvas.nativeElement.width, this.paperCanvas.nativeElement.height,
0, 0, this.imageCanvas.nativeElement.width, this.imageCanvas.nativeElement.height);
}
Don't know if this is the best way but it is a working solution.