Search code examples
canvasionic2paperjs

Ionic 2 with Paper JS - Flickering canvas and white box


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.


Solution

  • 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.