Skip to content

Commit 05fa376

Browse files
committed
feat(canvas): added options.fit for printCircularImage
1 parent 169398b commit 05fa376

File tree

3 files changed

+182
-12
lines changed

3 files changed

+182
-12
lines changed

.eslintrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
2-
"extends": ["@skyra", "plugin:prettier/recommended"],
2+
"extends": [
3+
"@skyra",
4+
"prettier/@typescript-eslint",
5+
"plugin:prettier/recommended"
6+
],
37
"rules": {
48
"@typescript-eslint/indent": "off"
59
}

src/lib/Canvas.ts

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ export interface GradientStop {
4545
color: string;
4646
}
4747

48+
export interface PrintCircularOptions {
49+
/**
50+
* The fit options, this is similar to CSS's object-fit.
51+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
52+
*/
53+
fit?: 'fill' | 'contain' | 'cover' | 'none';
54+
}
55+
4856
export type GlobalCompositeOperation = CanvasRenderingContext2D['globalCompositeOperation'];
4957
export type AntiAlias = CanvasRenderingContext2D['antialias'];
5058
export type TextDrawingMode = CanvasRenderingContext2D['textDrawingMode'];
@@ -643,17 +651,17 @@ export class Canvas {
643651
* @param height The height to draw the image in the destination canvas. This allows scaling of the drawn image. If not specified, the image is not scaled in height when drawn.
644652
* @param radius The radius for the circle
645653
*/
646-
public printCircularImage(imageOrBuffer: ImageResolvable, x: number, y: number, radius: number): this {
647-
const ratio = imageOrBuffer.width / imageOrBuffer.height;
648-
const [posX, posY, sizeX, sizeY] =
649-
ratio === 1
650-
? [x - radius, y - radius, radius * 2, radius * 2]
651-
: ratio > 1
652-
? [x - radius, y - radius / ratio, radius * 2, (radius * 2) / ratio]
653-
: [x - radius * ratio, y - radius, radius * 2 * ratio, radius * 2];
654+
public printCircularImage(
655+
imageOrBuffer: ImageResolvable,
656+
x: number,
657+
y: number,
658+
radius: number,
659+
{ fit = 'fill' }: PrintCircularOptions = {}
660+
): this {
661+
const { positionX, positionY, sizeX, sizeY } = Canvas.resolveCircularCoordinates(imageOrBuffer, x, y, radius, fit);
654662
return this.save()
655663
.createCircularClip(x, y, radius, 0, Math.PI * 2, false)
656-
.printImage(imageOrBuffer, posX, posY, sizeX, sizeY)
664+
.printImage(imageOrBuffer, positionX, positionY, sizeX, sizeY)
657665
.restore();
658666
}
659667

@@ -1702,4 +1710,77 @@ export class Canvas {
17021710
}
17031711
return wrappedText;
17041712
}
1713+
1714+
private static resolveCircularCoordinates(
1715+
imageOrBuffer: ImageResolvable,
1716+
x: number,
1717+
y: number,
1718+
radius: number,
1719+
fit: NonNullable<PrintCircularOptions['fit']>
1720+
): ResolvedCircularCoordinates {
1721+
const { width, height } = imageOrBuffer;
1722+
if (fit === 'none') {
1723+
return {
1724+
positionX: x - width / 2,
1725+
positionY: y - height / 2,
1726+
sizeX: width,
1727+
sizeY: height
1728+
};
1729+
}
1730+
1731+
const ratio = width / height;
1732+
const diameter = radius * 2;
1733+
1734+
if (fit === 'fill' || ratio === 1) {
1735+
return {
1736+
positionX: x - radius,
1737+
positionY: y - radius,
1738+
sizeX: diameter,
1739+
sizeY: diameter
1740+
};
1741+
}
1742+
1743+
if (fit === 'contain') {
1744+
return ratio > 1
1745+
? {
1746+
positionX: x - radius,
1747+
positionY: y - radius / ratio,
1748+
sizeX: diameter,
1749+
sizeY: diameter / ratio
1750+
}
1751+
: {
1752+
positionX: x - radius * ratio,
1753+
positionY: y - radius,
1754+
sizeX: diameter * ratio,
1755+
sizeY: diameter
1756+
};
1757+
}
1758+
1759+
if (ratio > 1) {
1760+
const sizeX = diameter * ratio;
1761+
const sizeY = diameter;
1762+
return {
1763+
positionX: x - sizeX / 2,
1764+
positionY: y - sizeY / 2,
1765+
sizeX,
1766+
sizeY
1767+
};
1768+
}
1769+
1770+
const sizeX = diameter;
1771+
const sizeY = diameter / ratio;
1772+
return {
1773+
positionX: x - sizeX / 2,
1774+
positionY: y - sizeY / 2,
1775+
sizeX,
1776+
sizeY
1777+
};
1778+
}
1779+
}
1780+
1781+
interface ResolvedCircularCoordinates {
1782+
positionX: number;
1783+
positionY: number;
1784+
sizeX: number;
1785+
sizeY: number;
17051786
}

tests/index.html

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,85 @@
11
<!DOCTYPE html>
2+
23
<head>
34
<title>Canvas Constructor</title>
5+
<style>
6+
td,
7+
th {
8+
border: 1px solid rgb(190, 190, 190);
9+
padding: 10px;
10+
}
11+
12+
td {
13+
text-align: center;
14+
}
15+
16+
tr:nth-child(even) {
17+
background-color: #eee;
18+
}
19+
20+
th[scope="col"] {
21+
background-color: #696969;
22+
color: #fff;
23+
}
24+
25+
th[scope="row"] {
26+
background-color: #d7d9f2;
27+
}
28+
29+
caption {
30+
padding: 10px;
31+
caption-side: bottom;
32+
}
33+
34+
table {
35+
border-collapse: collapse;
36+
border: 2px solid rgb(200, 200, 200);
37+
letter-spacing: 1px;
38+
font-family: sans-serif;
39+
font-size: .8rem;
40+
}
41+
</style>
442
</head>
43+
544
<body>
645
<h1>Canvas Constructor!</h1>
746
<canvas id="basic" width="300" height="300"></canvas>
847
<canvas id="image" width="300" height="300"></canvas>
9-
<script type="text/javascript" src="../dist/index.umd.js" ></script>
10-
<script type="text/javascript" >
48+
49+
<table>
50+
<thead>
51+
<tr>
52+
<th colspan="2">
53+
<h2>Circular Images</h2>
54+
</th>
55+
</tr>
56+
</thead>
57+
<tbody>
58+
<tr>
59+
<td>
60+
<h3>Fill</h3>
61+
<canvas id="circular-image-fill" width="300" height="300"></canvas>
62+
</td>
63+
<td>
64+
<h3>Contain</h3>
65+
<canvas id="circular-image-contain" width="300" height="300"></canvas>
66+
</td>
67+
</tr>
68+
<tr>
69+
<td>
70+
<h3>Cover</h3>
71+
<canvas id="circular-image-cover" width="300" height="300"></canvas>
72+
</td>
73+
<td>
74+
<h3>None</h3>
75+
<canvas id="circular-image-none" width="300" height="300"></canvas>
76+
</td>
77+
</tr>
78+
</tbody>
79+
</table>
80+
81+
<script type="text/javascript" src="../dist/index.umd.js"></script>
82+
<script type="text/javascript">
1183
const canvasElement = document.getElementById('basic');
1284
new CanvasConstructor.Canvas(canvasElement)
1385
.setColor('#AEFD54')
@@ -30,4 +102,17 @@ <h1>Canvas Constructor!</h1>
30102
.printText('Skyra!', 150, 280);
31103
})();
32104
</script>
105+
106+
<script type="text/javascript">
107+
(async () => {
108+
const image = await CanvasConstructor.resolveImage('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png')
109+
for (const fit of ['fill', 'contain', 'cover', 'none']) {
110+
const canvasElement = document.getElementById(`circular-image-${fit}`);
111+
new CanvasConstructor.Canvas(canvasElement)
112+
.setColor('#AEFD54')
113+
.printCircle(150, 150, 150)
114+
.printCircularImage(image, 150, 150, 150, { fit });
115+
}
116+
})();
117+
</script>
33118
</body>

0 commit comments

Comments
 (0)