What is the best way to incorporate an image into the canvas element and then use it as a drawing surface?

I have been searching for solutions on various platforms, but I'm having trouble finding ones that work specifically with Ionic and Angular.

One major issue I'm facing is trying to copy an image to the canvas. No matter what I try, I can't seem to get my image onto the canvas.

Below is the HTML code I am working with:

<ion-card>
  <ion-card-content>
    <p>Original Image:</p>
    <img id="originalPhoto" src="https://preview.ibb.co/jnW4Qz/Grumpy_Cat_920x584.jpg" width=400 height=400 />
  </ion-card-content>
</ion-card>

<ion-card>
  <ion-card-content>
    <canvas id="canvas" width="240" height="297" style="border:1px solid #d3d3d3;"></canvas>
  </ion-card-content>
</ion-card>

.ts

drawOnPhoto() { 
  const canvas = document.getElementById("canvas") as HTMLCanvasElement;
  console.log("canvas", canvas); // returns null
  const ctx = canvas.getContext("2d");
  console.log("ctx",ctx); //returns null
  const img = document.getElementById("originalPhoto");
  console.log("img", img); // returns null
  ctx.drawImage(img, 10, 10);
  /*
  img, error
  Type 'HTMLElement' is missing the following properties from type 
  'HTMLCanvasElement': height, width, captureStream, getContext, 
  and 2 more.
  */
}

Answer №1

After experimenting with an ionic capacitor project, I discovered a setup that works well. In this configuration, I have integrated a base64 image as the background and included drawing tools on a separate layer.

html

<ion-card>
  <ion-card-content>
    <ion-row>
        <ion-col *ngFor="let color of colors" [style.background]="color" class="color-block" tappable
          (click)="selectColor(color)"></ion-col>
    </ion-row>

    <ion-radio-group [(ngModel)]="selectedColor">
      <ion-row>
        <ion-col *ngFor="let color of colors" class="ion-text-center">
          <ion-radio [value]="color"></ion-radio>
        </ion-col>
      </ion-row>
    </ion-radio-group>

    <ion-range min="2" max="20" color="primary" [(ngModel)]="lineWidth">
      <ion-icon size="small" slot="start" name="brush"></ion-icon>
      <ion-icon slot="end" name="brush"></ion-icon>
    </ion-range>

    <canvas #imageCanvas (touchstart)="startDrawing($event)" (touchmove)="moved($event)" (touchend)="endDrawing()"> </canvas>
  </ion-card-content>
</ion-card>

scss

canvas {
  border: 1px solid rgb(187, 178, 178);
}

.color-block {
  height: 40px;
}

ts

export class QuestionPhotosPage {
  @ViewChild('imageCanvas', { static: false }) canvas: any;
  canvasElement: any;
  saveX: number;
  saveY: number;
  selectedColor = '#9e2956';
  colors = [ '#9e2956', '#c2281d', '#de722f', '#edbf4c', '#5db37e', '#459cde', '#4250ad', '#802fa3' ];
  drawing = false;
  lineWidth = 5;

constructor(private platform: Platform) {}

// used for changing color of brush
selectColor(color) { this.selectedColor = color }

// used to load the background photo and prepare canvas
drawOnPhoto(file: { data: any; }) {
  this.file = file.data; // Using a base64 jpeg here
  setTimeout(() => { // Added timeout to allow photo to finish loading 
    // Set the Canvas Element and its size
    this.canvasElement = this.canvas.nativeElement;
    this.canvasElement.width = this.platform.width() + '';
    this.canvasElement.height = 200;
    var background = new Image();
    background.src = this.file; // Background image to be used
    let ctx = this.canvasElement.getContext('2d');
    background.onload = () => { ctx.drawImage(background,0,0, this.canvasElement.width, this.canvasElement.height);  }
  }, 250);
}

// sets initial drawing point
startDrawing(ev) {
  this.drawing = true;
  let canvasPosition = this.canvasElement.getBoundingClientRect();
  this.saveX = ev.touches[0].pageX - canvasPosition.x;
  this.saveY = ev.touches[0].pageY - canvasPosition.y;
}

// listens for movement and adds points and joins 
moved(ev) { 
  console.log("this.lineWidth", this.lineWidth)
  if (!this.drawing) return;
  var canvasPosition = this.canvasElement.getBoundingClientRect();
  let ctx = this.canvasElement.getContext('2d');
  let currentX = ev.touches[0].pageX - canvasPosition.x;
  let currentY = ev.touches[0].pageY - canvasPosition.y;
  ctx.lineJoin = 'round';
  ctx.strokeStyle = this.selectedColor;
  ctx.lineWidth = this.lineWidth;
  ctx.beginPath();
  ctx.moveTo(this.saveX, this.saveY);
  ctx.lineTo(currentX, currentY);
  ctx.closePath();
  ctx.stroke();
  this.saveX = currentX;
  this.saveY = currentY;
}

// when finger removed stops the drawing
endDrawing() { this.drawing = false }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Typescript errors is reporting an issue with implementing the interface because the type 'Subject<boolean>' is not compatible with 'Subject<boolean>'

Creating an Angular 2 and Typescript application. I am facing an issue with an abstract class within an NPM package that I am trying to implement in my app code. Everything was functioning correctly until I introduced the public isLoggedIn:Subject<bool ...

Tips for changing a function signature from an external TypeScript library

Is it possible to replace the function signature of an external package with custom types? Imagine using an external package called translationpackage and wanting to utilize its translate function. The original function signature from the package is: // ...

Limiting the parameter type in Node.js and TypeScript functions

Currently working on a Node.js project utilizing TypeScript. I'm attempting to limit the argument type of a function to a specific base class. As a newcomer to both Node and TypeScript with a background in C#, I may not fully grasp some of the langua ...

Restrict or define the acceptable values for a key within an interface

In search of defining an interface that allows for specific range of values for the key. Consider this example: interface ComparisonOperator { [operator: string]: [string, string | number]; } The key can take on values such as >, >=, !=, and so ...

Overlooking errors in RxJs observables when using Node JS SSE and sharing a subscription

There is a service endpoint for SSE that shares a subscription if the consumer with the same key is already subscribed. If there is an active subscription, the data is polled from another client. The issue arises when the outer subscription fails to catch ...

Using regular expressions, replace all instances of " " with ' ' based on certain conditions

I am looking to replace quotes "" with single quotes '' within a string. string = `bike "car" bus "'airplane'" "bike" "'train'"` If a word is inside double quotes, it shoul ...

Angular2 allows you to create pipes that can filter multiple values from JSON data

My program deals with an array of nested json objects that are structured as follows: [{name: {en:'apple',it:'mela'}},{name:{en:'coffee',it:'caffè'}}] I am looking to implement a pipe that can filter out objects b ...

Overriding a generic property in Typescript allows for a more

I'm troubleshooting an issue with my code: class Base<T> {} class Test { prop: Base<any>; createProp<T>() { this.prop = new Base<T>(); } } const test = new Test(); test.createProp<{ a: number }>(); test.pr ...

Guide to uploading a recorded audio file (Blob) to a server using ReactJS

I'm having trouble using the react-media-recorder library to send recorded voice as a file to my backend. The backend only supports mp3 and ogg formats. Can anyone provide guidance on how to accomplish this task? Your help would be greatly appreciated ...

Showing a header 2 element just once using both *ngFor and *ngIf in Angular

I have an array of words with varying lengths. I am using ng-For to loop through the array and ng-if to display the words based on their lengths, but I am struggling to add titles to separate them. Expected Outcome: 2 letter words: to, am, as... 3 lette ...

Deactivate the button if the mat-radio element is not selected

Here is my setup with a mat-radio-group and a button: <form action=""> <mat-radio-group aria-label="Select an option"> <mat-radio-button value="1">Option 1</mat-radio-button> <mat-radio-b ...

Display an API generated popup list using Vue's rendering capabilities

I'm attempting to generate a pop-up within a displayed list using custom content retrieved from an API request. Currently, my code looks like this: <template> <div class="biblio__all"> <a v-for="i in items" ...

Exploring the Differences Between Native Script and Ionic: A Guide to Choosing the Right Framework for Your Hybrid Apps

When deciding between Ionic and Native Script for hybrid app development, which technology would you recommend? Or do you have another suggestion knowing that I am familiar with Angular 6? Also, I am looking for a Native Script tutorial, preferably in vide ...

Transitioning an NX environment to integrate ESM

My NX-based monorepo is quite extensive, consisting of half a dozen apps, frontend, backend, and dozens of libs. Currently, everything is set up to use commonjs module types, as that's what the NX generators have always produced. However, many librar ...

Definitions for images in the following format

I am currently utilizing typescript in conjunction with NextJs and next-images. Here is the code snippet: import css from "./style.sass"; import img from './logo.svg'; import Link from 'next/link'; export default () => <Link hre ...

Error message: The database query function is encountering an issue where the property 'relation.referencedTable' is undefined and cannot be accessed

Currently, I am working with two schemas named products.ts and category.ts. The relationship between these files is defined as one-to-many. In the products.ts file: import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import ...

Is it possible to define TypeScript interfaces in a separate file and utilize them without the need for importing?

Currently, I find myself either declaring interfaces directly where I use them or importing them like import {ISomeInterface} from './somePlace'. Is there a way to centralize interface declarations in files like something.interface.ts and use the ...

Social sharing functionality is now available through the combination of a camera plugin and

I'm trying to figure out how to share a photo taken using the camera plugin with the cordova social sharing plugin in my app. The camera functionality is working well and displays the captured image. ionicApp.controller("functions", function($scope, ...

EventListener cannot be removed

My TypeScript class is structured like this: class MyClass { let canvas: any; constructor(canvas: any) { this.canvas = canvas; this.canvas.requestPointerLock = this.canvas.requestPointerLock; document.exitPointerLock = ...

TypeScript types for d3 version 4

I am currently working on a d3 project and to simplify the development process and reduce errors, I decided to implement TypeScript. Using d3 v4, I initially faced issues with incorrect type definitions which led to errors like d3 not having a definition ...