This article explains how to flatten text onto an image in Ionic using a basic HTML Canvas element.
Brendan Thompson
Tags: blog Ionic
In order to develop an Ionic application for a client I had to learn how to overlay text onto an image and then flatten it into one new image that the user could share. I spent a ton of time researching this and could not find any real good posts where this was done in Ionic or any other web technology. Luckily a friend of mine recommended that I check out the native HTML Canvas element which is able to handle the task quite nicely.
The HTML Canvas element makes this whole process fairly straightforward. Basically the markup needs a canvas
element to do the creation and an img
element to display the flattened result. All of the hard work is done in the TypeScript.
The pattern that I implemented is to have an invisible canvas
element where the whole thing is drawn and then just a simple img
element to display the result. There are two important things to note here. First of all, the src
for the image is an object called finalImage
which we will declare in the TypeScript later. Secondly, the canvas has the id #myCanvas
which we will use later to access the element within the code. For testing it may be a wise to remove the style that hides the canvas (style="display:none;"
).
<ion-card class="main-display">
<img [attr.src]="finalImage">
</ion-card>
<canvas #myCanvas style="display: none;"></canvas>
In order for the TypeScript to connect to the canvas element we will use the angular core dependencies ElementRef
& ViewChild
.
import { ElementRef, ViewChild } from '@angular/core';
In order to control the canvas we will need to connect to the element and then manage both the instance of it and the context of it. Notice that we are using selectedPicture
to store the path to the image we will be using.
@ViewChild('myCanvas') canvasEl: ElementRef;
private theCanvas: any;
private theContext: any;
private finalImage: any;
private selectedPicture: any = 'assets/imgs/myImage.jpg'
These variables will be used later when drawing the actual canvas. The values could be hardcoded in place, but abstracting these values away into special variables allows for much simpler customization after the fact. These will all make a lot more sense once we start drawing the image and text onto the canvas.
// Canvas
private canvasColor = "rgba(255, 255, 255, 1)";
private pictureWidth = 760;
private pictureHeight = 1400;
// Text
private textX = this.pictureWidth/2;
private textY = this.pictureHeight - (this.pictureHeight/4);
private textWrapWidth = this.pictureWidth - (this.pictureWidth/9.00);
private textWrapHeight = 120;
private fontStyle = "italic 70px Arial";
private fontColor = "rgba(255, 255, 255, 1)"; // For fillText
private borderWidth = 2.2;
private borderColor = "rgba(0, 0, 0, 1)"; // For strokeText border
The initialization of the canvas just involves connecting our theCanvas
element to the canvasEl
from the interface, setting the dimensions, and connecting to the context. At this point everything is very sequential and relies on the previous steps being completed first. It all starts with the Ionic lifecycle method ionViewDidLoad()
where we call our new initializeCanvas()
function.
initialiseCanvas(){
this.theCanvas = this.canvasEl.nativeElement;
this.theCanvas.width = this.pictureWidth;
this.theCanvas.height = this.pictureHeight;
if(this.theCanvas.getContext){
this.theContext = this.theCanvas.getContext('2d');
this.drawTheImage();
}
}
Step 4 leaves off with connecting theContext
to the context of the canvas and then calling this new function drawTheImage()
. Whenever the whole thing needs to be re-drawn, this is the function to call. It starts by clearing the whole thing to start fresh. Then it gives it a background color before drawing the actual image. That math in the parameter of this.theContext.drawImage(...)
puts the image into the center of the canvas.
drawTheImage(){
// Clear Current Image
this.theContext.clearRect(0, 0, this.theCanvas.width, this.theCanvas.height);
// Fill background
this.theContext.fillStyle = this.canvasColor;
this.theContext.fillRect(0, 0, this.theCanvas.width, this.theCanvas.height);
// Display the Image
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = (event) => {
this.theContext.drawImage(img, this.theCanvas.width/2 - img.width/2, this.theCanvas.height/2 - img.height/2);
this.drawText();
};
img.src=this.selectedPicture;
}
Once the image has been drawn it is time to draw the text on top of it. The fill
is the bulk of the font while the stroke
is the border. In order to implement text wrapping we have to call another function to draw the lines to fit. I got the text wrapping procedure from an old canvas tutorial.
drawText(){
this.theContext.font = this.fontStyle;
this.theContext.textAlign = "center";
this.theContext.fillStyle = this.fontColor; // fill text
this.theContext.lineWidth = this.borderWidth;
this.theContext.strokeStyle = this.borderColor; // stroke border
this.wrapText();
this.saveCanvas();
}
wrapText() {
var words = this.currentText.text.split(' ');
var line = '';
var currentTextY = this.textY;
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = this.theContext.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > this.textWrapWidth && n > 0) {
this.theContext.fillText(line, this.textX, currentTextY); // fill text
this.theContext.strokeText(line, this.textX, currentTextY); // stroke border
line = words[n] + ' ';
currentTextY += this.textWrapHeight;
}
else {
line = testLine;
}
}
this.theContext.fillText(line, this.textX, currentTextY); // fill text
this.theContext.strokeText(line, this.textX, currentTextY); // stroke border
}
Once the canvas is drawn successfully simply set the finalImage
member variable to the canvas as a url.
this.finalImage = this.theCanvas.toDataURL();
Flattening text onto an image is really a rather straightforward process. Basically just connect to the canvas element from the interface template, get the context of it in order to draw the image and the text, and once it is ready just save the canvas out to an image. If you have any questions feel free to contact me. Also, there are a lot of helpful basic Canvas resource online. Good luck!