I'm playing with drawing on html canvas and I'm little confused of how different coordinate systems actually works. What I have learned so far is that there are more coordinate systems:
canvas coordinate system
css coordinate system
physical (display) coordinate system
So when I draw a line using CanvasRenderingContext2D
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(3, 1);
ctx.lineTo(3, 5);
ctx.stroke();
before drawing pixels to the display, the path needs to be
scaled according to the ctx transformation matrix (if any)
scaled according to the ratio between css canvas element dimensions (canvas.style.width and canvas.style.height) and canvas drawing dimensions (canvas.width and canvas.height)
scaled according to the window.devicePixelRatio (hi-res displays)
Now when I want to draw a crisp line, I found that there are two things to fight with. The first one is that canvas uses antialiasing. So when I draw a line of thikness 1 at integer coordinates, it will be blurred.
To fix this, it needs to be shifted by 0.5 pixels
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
The second thing to consider is window.devicePixelRatio. It is used to map logical css pixels to physical pixels. The snadard way how to adapt canvas to hi-res devices is to scale to the ratio
const ratio = window.devicePixelRatio || 1;
const clientBoundingRectangle = canvas.getBoundingClientRect();
canvas.width = clientBoundingRectangle.width * ratio;
canvas.height = clientBoundingRectangle.height * ratio;
const ctx = canvas.getContext('2d');
ctx.scale(ratio, ratio);
My question is, how is the solution of the antialiasing problem related to the scaling for the hi-res displays?
Let's say my display is hi-res and window.devicePixelRatio is 2.0. When I apply context scaling to adapt canvas to the hi-res display and want to draw the line with thickness of 1, can I just ignore the context scale and draw
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
which is in this case effectively
ctx.moveTo(7, 2);
ctx.lineTo(7, 10);
or do I have to consider the scaling ratio and use something like
ctx.moveTo(3.75, 1);
ctx.lineTo(3.75, 5);
to get the crisp line?
Antialiasing can occur both in the rendering on the canvas bitmap buffer, at the time you draw to it, and at the time it's displayed on the monitor, by CSS.
The 0.5px offset for straight lines works only for line widths that are odd integers. As you hinted to, it's so that the stroke, that can only be aligned to the center of the path, and thus will spread inside and outside of the actual path by half the line width, falls on full pixel coordinates. For a comprehensive explanation, see this previous answer of mine.
Scaling the canvas buffer to the monitor's pixel ratio works because on high-res devices, multiple physical dots will be used to cover a single px area. This allows to have more details e.g in texts, or other vector graphics. However, for bitmaps this means the browser has to "pretend" it was bigger in the first place. For instance a 100x100 image, rendered on a 2x monitor will have to be rendered as if it was a 200x200 image to have the same size as on a 1x monitor. During that scaling, the browser may yet again use antialiasing, or another scaling algorithm to "create" the missing pixels.
By directly scaling up the canvas by the pixel ratio, and scaling it down through CSS, we end up with an original bitmap that's the size it will be rendered, and there is no need for CSS to scale anything anymore.
But now, your canvas context is scaled by this pixel ratio too, and if we go back to our straight lines, still assuming a 2x monitor, the 0.5px offset now actually becomes a 1px offset, which is useless. A lineWidth of 1 will actually generate a 2px stroke, which doesn't need any offset.
So no, don't ignore the scaling when offsetting your context for straight lines.
But the best is probably to not use that offset trick at all, and instead use rect() calls and fill() if you want your lines to fit perfectly on pixels.
const canvas = document.querySelector("canvas");
// devicePixelRatio may not be accurate, see below
setCanvasSize(canvas);
function draw() {
const dPR = devicePixelRatio;
const ctx = canvas.getContext("2d");
// scale() with weird zoom levels may produce antialiasing
// So one might prefer to do the scaling of all coords manually:
const lineWidth = Math.round(1 * dPR);
const cellSize = Math.round(10 * dPR);
for (let x = cellSize; x < canvas.width; x += cellSize) {
ctx.rect(x, 0, lineWidth, canvas.height);
}
for (let y = cellSize; y < canvas.height; y += cellSize) {
ctx.rect(0, y, canvas.width, lineWidth);
}
ctx.fill();
}
function setCanvasSize(canvas) {
// We resize the canvas bitmap based on the size of the viewport
// while respecting the actual dPR
// Thanks to gman for the reminder of how to suppport all early impl.
// https://stackoverflow.com/a/65435847/3702797
const observer = new ResizeObserver(([entry]) => {
let width;
let height;
const dPR = devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
if ( entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize * dPR;
height = entry.contentBoxSize[0].blockSize * dPR;
} else {
width = entry.contentBoxSize.inlineSize * dPR;
height = entry.contentBoxSize.blockSize * dPR;
}
} else {
width = entry.contentRect.width * dPR;
height = entry.contentRect.height * dPR;
}
canvas.width = width;
canvas.height = height;
canvas.style.width = (width / dPR) + 'px';
canvas.style.height = (height / dPR) + 'px';
// we need to redraw
draw();
});
// observe the scrollbox size changes
try {
observer.observe(canvas, { box: 'device-pixel-content-box' });
}
catch(err) {
observer.observe(canvas, { box: 'content-box' });
}
}
canvas { width: 300px; height: 150px; }
<canvas></canvas>
Preventing anti-aliasing requires that the pixels of the canvas, which is a raster image, are aligned with the pixels of the screen, which can be done by multiplying the canvas size by the devicePixelRatio, while using the CSS size to hold the canvas to its original size:
canvas.width = pixelSize * window.devicePixelRatio;
canvas.height = pixelSize * window.devicePixelRatio;
canvas.style.width = pixelSize + 'px';
canvas.style.height = pixelSize + 'px';
You can then use scale on the context, so that the drawn images won't be shrunk by higher devicePixelRatios. Here I am rounding so that lines can be crisp on ratios that are not whole numbers:
let roundedScale = Math.round(window.devicePixelRatio);
context.scale(roundedScale, roundedScale);
The example then draws a vertical line from the center top of one pixel to the center top of another:
context.moveTo(100.5, 10);
context.lineTo(100.5, 190);
One thing to keep in mind is zooming. If you zoom in on the example, it will become anti-aliased as the browser scales up the raster image. If you then click run on the example again, it will become crisp again (on most browsers). This is because most browsers update the devicePixelRatio to include any zooming. If you are rendering in an animation loop while they are zooming, the rounding could cause some flickering.
Related
I'm trying to make a pixel editor with 2 canvas. The first canvas displays a second canvas which contains the pixels. The first canvas uses drawImage to position and scale the second canvas.
When the second canvas is scaled smaller than it's original size, it starts to glitch.
Here is the canvas displayed at it's original size. When I zoom in, the second canvas get bigger and everything works perfectly.
However when I zoom out, the grid and the background (transparency) act very strangely.
To draw the second canvas on the first canvas, I use the function
ctx.drawImage(drawCanvas, offset.x, offset.y, width * pixelSize, height * pixelSize);
I have read that scaling in multiple iterations might give a better quality with images but I am not sure about a canvas.
I could fully redraw the second canvas in a lower resolution when the user zooms out, but it is a bit heavy on the cpu.
Is there any better solution that I don't know of?
Your problem comes from anti-aliasing.
Pixels aren't sub-divisible, and when you ask the computer to draw something outside of the pixel boundaries, it will try its best to render something that usually looks good to eyes, by mixing the colors so that what should have been a black 0.1 pixel line will become a light-gray pixel for instance.
This generally works good, particularly with pictures of the real word, or complex shapes like circles. However with grids... That's not so great as you experienced it.
Your case is dealing with two different cases, and you will have to deal with hem separately.
In the canvas 2D API (and a lot of 2D APIs) stroke do bleed from both sides of the coordinates you did set it. So when drawing lines of 1px wide, you need to account for a 0.5px offset to be sure it won't get rendered as two gray pixels. For more info about this, see this answer. You are probably using such a stroke for the grid.
fill on the other hand only covers the inside of the shape, so if you fill a rectangle, you need to not offset its coords from the px boundaries. This is required for the checkerboard.
Now, for boh these drawings, the best is probably to use patterns. You only need to draw a small version of it, and then the pattern will repeat it automatically, saving a lot of computation.
Scaling of a pattern can be done by calling the transform methods of the 2D context. We can even take advantage of the closest-neighbor algorithm to avoid antialising when drawing this pattern by setting the imageSmoothingEnabled property to false.
However for our grid, we may want to keep the lineWidth constant. For this we will need to generate a new pattern at every draw call.
// An helper function to create CanvasPatterns
// returns a 2DContext on which a simple `finalize` method is attached
// method which does return a CanvasPattern from the underlying canvas
function patternMaker(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.finalize = (repetition = "repeat") => ctx.createPattern(canvas, repetition);
return ctx;
}
// The checkerboard can be generated only once
const checkerboard_patt_maker = patternMaker(2, 2);
checkerboard_patt_maker.fillStyle = "#CCC";
checkerboard_patt_maker.fillRect(0,0,1,1);
checkerboard_patt_maker.fillRect(1,1,1,1);
const checkerboard_patt = checkerboard_patt_maker.finalize();
// An helper function to create grid patterns
// Since we want a constant lineWidth, no matter the zoom level
function makeGridPattern(width, height) {
width = Math.round(width);
height = Math.round(height);
const grid_patt_maker = patternMaker(width, height);
grid_patt_maker.lineWidth = 1;
// apply the 0.5 offset only if we are on integer coords
// for instance a <3,3> pattern wouldn't need any offset, 1.5 is already perfect
const x = width/2 % 1 ? width/2 : width/2 + 0.5;
const y = height/2 % 1 ? height/2 : height/2 + 0.5;
grid_patt_maker.moveTo(x, 0);
grid_patt_maker.lineTo(x, height);
grid_patt_maker.moveTo(0, y);
grid_patt_maker.lineTo(width, y);
grid_patt_maker.stroke();
return grid_patt_maker.finalize();
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const checkerboard_input = document.getElementById('checkerboard_input');
const grid_input = document.getElementById('grid_input');
const connector = document.getElementById('connector');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const checkerboard_zoom = checkerboard_input.value;
const grid_zoom = grid_input.value;
// we generate a new pattern for the grid, so the lineWidth is always 1
const grid_patt = makeGridPattern(grid_zoom, grid_zoom);
// draw once the rectangle covering the whole canvas
// with normal transforms
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
// the checkerboard
ctx.fillStyle = checkerboard_patt;
// our path is already drawn, we can control only the fill
ctx.scale(checkerboard_zoom, checkerboard_zoom);
// avoid antialiasing when painting our pattern (similar to rounding the zoom level)
ctx.imageSmoothingEnabled = false;
ctx.fill();
// done, reset to normal
ctx.imageSmoothingEnabled = true;
ctx.setTransform(1, 0, 0, 1, 0, 0);
// paint the grid
ctx.fillStyle = grid_patt;
// because our grid is drawn in the middle of the pattern
ctx.translate(Math.round(grid_zoom/2), Math.round(grid_zoom/2));
ctx.fill();
// reset
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
draw();
checkerboard_input.oninput = grid_input.oninput = function(e) {
if(connector.checked) {
checkerboard_input.value = grid_input.value = this.value;
}
draw();
};
connector.oninput = e => checkerboard_input.oninput();
<label>checkerboard-layer zoom<input id="checkerboard_input" type="range" min="2" max="50" step="0.1"></label><br>
<label>grid-layer zoom<input id="grid_input" type="range" min="2" max="50" step="1"></label><br>
<label>connect both zooms<input id="connector" type="checkbox"></label>
<canvas id="canvas"></canvas>
Is there any way to scale drawing elements on a canvas? I'm creating an application where then user can place points on a canvas, but when I try to resize the browser window all the elements disappear.
My initial tentative was to calculate the screen difference before and after the resizing. After I get the percentages, I just sat the scale on the canvas and place the coordinates that was saved from the first time I drew on the canvas, but still doesn't work, if the points appear on the canvas, it is on the same place without scaling. Can someone give me little line of thought?
private calculateCanvasScreenDifference(previousSize, guestScreen) {
return ((controlScreen - currentSize) * 100) / controlScreen;
}
let difWidthPercent = Math.abs(this.calculateCanvasScreenDifference(canvasPreviousWidth, canvasWidth) * 0.01);
let difHeightPercent = Math.abs(this.calculateCanvasScreenDifference(canvasPreviousHeight, canvasHeight) * 0.01);
let scaleX = ((Math.abs(difWidthPercent) <= 1) ? 1.00 - difWidthPercent : difWidthPercent - 1.00);
let scaleY = ((Math.abs(difHeightPercent) <= 1) ? 1.00 - difHeightPercent : difHeightPercent - 1.00);
this.cx.scale(Number(scaleX), Number(scaleY));
...
...
// then start recreating the drawing that was previous saved on an array of object(x, y values)
this.cx.beginPath();
this.cx.arc(coord.x, coord.y, 7, 0, Math.PI * 2, true);
this.cx.stroke();
Keep track of your canvas width and start with a scale factor of 1.
let originalWidth = canvas.width;
let scale = 1;
On resize calculate the new scale factor. And update tracked canvas size.
let scale = newWidth / originalWidth;
originalWidth = newWidth;
Use the scale factor for all drawing at all times. e.g.
context.arc(coord.x * scale, coord.y * scale, radius, 0, Math.PI*2, false);
Note: This approach assumes the original and new canvas sizes are proportional. If not then you will need to track width and height, and calculate separate x and y scale factors.
A couple of questions related to mine have been asked here, but still I am struggling.
I use html2canvas to make a canvas of certain parts of my webapp. Here I have the first question: the documentation gives a number of options (width, height, scale) which give the impression that they are intended to manipulate the size of the resulting image. I would like to reduce the size of the result, compared to the original div. However, whatever I do, I don't get it. If I set width / height smaller than the original div, it simply takes only part of the div (it basically crops). And scale seems to work on the ratio between canvas width/height and canvas css style width/height. It does not really scale down contents from the original size onto a smaller canvas.
Well, I could live with that and go forward with the resulting canvas at same size as original div, since there exist another good option for scaling of images on client side.
I try to redraw the canvas on a smaller one, using drawImage with proper context.width, context.height and context.scale.It works but whatever I try, the endresult is rather blurry. Note that I am DOWNscaling (not even UPscaling) simple images, not high-res pictures. A screenshot of the result:
On top the original div, just below that the unscaled result after html2canvas and on the right the down-scaled after:
ctx.drawImage(canvas, 0, 0, width, height);
My javascript:
function init() {
$('#testBtn').on('click', function(){proces();});
}
function proces() {
var testElem = document.getElementById("container");
var options = {
//I could play here with width, height and scale
};
html2canvas(testElem, options).then(function(canvas) {
canvas.id = "resultCanvas";
document.body.appendChild(canvas);
var width = canvas.width;
var height = canvas.height;
var scale = 0.5 / 1.25;
console.log(width, height)
//var scaledCanvas = document.getElementById("testScaledCanvas");
var scaledCanvas = document.createElement("canvas");
scaledCanvas.id = "testScaledCanvas";
scaledCanvas.height = scale * height;
scaledCanvas.width = scale * width;
var ctx = scaledCanvas.getContext("2d");
ctx.scale(scale, scale);
ctx.webkitImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
//Draw the scaled canvas
ctx.drawImage(canvas, 0, 0, width, height);
document.body.appendChild(scaledCanvas);
});
}
So to summarize, downscaling a canvas results in blurry images. And I fail to get html2canvas to properly squeeze a div on a smaller canvas. It only crops.
Any help would be greatly appreciated!
Mark
I am trying to create a simple canvas grid which will fit itself to the player's current zoom level, but also to a certain canvas height/width proportional screen limit. Here is what I got so far:
JS:
var bw = window.innerWidth / 2; //canvas size before padding
var bh = window.innerHeight / 1.3; //canvas size before padding
//padding around grid, h and w
var pW = 30;
var pH = 2;
var lLimit = 0; //9 line limit for both height and width to create 8x8
//size of canvas - it will consist the padding around the grid from all sides + the grid itself. it's a total sum
var cw = bw + pW;
var ch = bh + pH;
var canvas = $('<canvas/>').attr({width: cw, height: ch}).appendTo('body');
var context = canvas.get(0).getContext("2d");
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) { //handling the height grid
context.moveTo(x, 0);
context.lineTo(x, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) { //handling the width grid
context.moveTo(0, x); //begin the line at this cord
context.lineTo(bw, x); //end the line at this cord
lLimit++;
}
//context.lineWidth = 0.5; what should I put here?
context.strokeStyle = "black";
context.stroke();
}
drawBoard();
Now, I succeeded at making the canvas to be at the same proportional level for each screen resolution zoom level. this is part of what I am trying to achieve. I also try to achieve thin lines, which will look the same at all different zooming levels, and of course to remove the blurriness. right now the thickness
of the lines change according to the zooming levels and are sometimes blurry.
Here is jsFiddle (although the jsFiddle window itself is small so you will barely notice the difference):
https://jsfiddle.net/wL60jo5n/
Help will be greatly appreciated.
To prevent blur, you should account for window.devicePixelRatio when setting dimensions of your canvas element (and account for that dimensions during subsequent drawing, of course).
width and height properties of your canvas element should contain values that are proportionally higher than values in CSS properties of the same names. This can be expressed e.g. as the following function:
function setCanvasSize(canvas, width, height) {
var ratio = window.devicePixelRatio,
style = canvas.style;
style.width = '' + (width / ratio) + 'px';
style.height = '' + (height / ratio) + 'px';
canvas.width = width;
canvas.height = height;
}
To remove blurry effect on canvas zoom/scale i used image-rendering: pixelated in css
The problem is that you are using decimal values to draw. Both the canvas width and the position increments in your drawBoard() loop use fractions. The canvas is a bitmap surface, not a vectorial drawing. When you set the width and height of the canvas, you set the actual number of pixels stored in memory. That value cannot be decimal (browsers will probably just trim the decimal part). When you try to draw at decimal positions, the canvas will use pixel interpolation to avoid aliasing, hence the occasional blur.
See a version where I round x before drawing:
https://jsfiddle.net/hts7yybm/
Try rounding the values just before you draw them, but not in your actual logic. That way, the imprecision won't stack as the algorithm keeps adding to the value.
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) {
var roundedX = Math.round(x);
context.moveTo(roundedX, 0);
context.lineTo(roundedX, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) {
var roundedX = Math.round(x);
context.moveTo(0, roundedX);
context.lineTo(bw, roundedX);
lLimit++;
}
context.lineWidth = 1; // never use decimals
context.strokeStyle = "black";
context.stroke();
}
EDIT: I'm pretty sure all browsers behave as if the canvas was an img element, so there's no way to prevent aliasing when the user zooms with their browser's zoom function, other than with prefixed css. And even then, I'm not sure the browsers's zoom feature takes that into account.
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
Also, make sure the canvas doesn't have any CSS-set dimensions. That only stretches the image after it's been drawn instead of increasing the drawing surface. If you want to fill a block with the canvas by giving it 100% width and height, then you need some JS to compute the CSS-given height and width and set the value of the canvas's width and height property based on that. Then you can make your own implementation of a zoom function within your canvas drawing code, but depending on what you're doing it might be overkill.
I'm drawing a line chart with canvas. The chart is responsive, but the line has to have a fixed width.
I made it responsive with css
#myCanvas{
width: 80%;
}
,so the stroke is scaled.
The only solution I have found is to get the value of the lineWidth with the proportion between the width attribute of the canvas and its real width.
To apply it, I clear and draw the canvas on resize.
<canvas id="myCanvas" width="510" height="210"></canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw(){
var canvasattrwidth = $('#myCanvas').attr('width');
var canvasrealwidth = $('#myCanvas').width();
// n sets the line width
var n = 4;
var widthStroke = n * (canvasattrwidth / canvasrealwidth) ;
ctx.lineWidth = widthStroke;
ctx.beginPath();
ctx.moveTo( 0 , 10 );
ctx.lineTo( 200 , 100 );
ctx.stroke();
}
$(window).on('resize', function(){
ctx.clearRect(0, 0, c.width, c.height);
draw();
});
draw();
</script>
This is my first canvas and I think there is an easier way to made the lineWidth fixed (not to clear and draw everytime on resize), but I cannot find it.
There is a question with the similar problem
html5 canvas prevent linewidth scaling
but with the method scale(), so I cannot use that solution.
There is no way to get a real world dimension of details for the canvas such as millimeters or inches so you will have to do it in pixels.
As the canvas resolution decreases the pixel width of a line needs to decrease as well. The limiting property of line width is a pixel. Rendering a line narrower than a pixel will only approximate the appearance of a narrower line by reducing the opacity (this is done automatically)
You need to define the line width in terms of the lowest resolution you will expect, within reason of course and adjust that width as the canvas resolution changes in relation to this selected ideal resolution.
If you are scaling the chart by different amounts in the x and y directions you will have to use the ctx.scale or ctx.setTransform methods. As you say you do not want to do this I will assume that your scaling is always with a square aspect.
So we can pick the lowest acceptable resolution. Say 512 pixels for either width or height of the canvas and select the lineWidth in pixels for that resolution.
Thus we can create two constants
const NATIVE_RES = 512; // the minimum resolution we reasonably expect
const LINE_WIDTH = 1; // pixel width of the line at that resolution
// Note I Capitalize constants, This is non standard in Javascript
Then to calculate the actual line width is simply the actual canvas.width divided by the NATIVE_RES then multiply that result by the LINE_WIDTH.
var actualLineWidth = LINE_WIDTH * (canvas.width / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You may want to limit that size to the smallest canvas dimension. You can do that with Math.min or you can limit it in the largest dimension with Math.max
For min dimention.
var actualLineWidth = LINE_WIDTH * (Math.min(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
For max dimension
var actualLineWidth = LINE_WIDTH * (Math.max(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You could also consider the diagonal as the adjusting factor that would incorporate the best of both x and y dimensions.
// get the diagonal resolution
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = LINE_WIDTH * (diagonalRes / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
And finally you may wish to limit the lower range of the line to stop strange artifacts when the line gets smaller than 1 pixel.
Set lower limit using the diagonal
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = Math.max(1, LINE_WIDTH * (diagonalRes / NATIVE_RES));
ctx.lineWidth = actualLineWidth;
This will create a responsive line width that will not go under 1 pixel if the canvas diagonal resolution goes under 512.
The method you use is up to you. Try them out a see what you like best. The NATIVE_RES I picked "512" is also arbitrary and can be what ever you wish. You will just have to experiment with the values and method to see which you like best.
If your scaling aspect is changing then there is a completely different technique to solve that problem which I will leave for another question.
Hope this has helped.