Skip to content

Commit 1589803

Browse files
committed
refactor ascii image conversio
1 parent c9bc079 commit 1589803

File tree

2 files changed

+27
-71
lines changed

2 files changed

+27
-71
lines changed

src/commands/context/ascii-image.mjs

-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ export const execute = async (interaction) => {
3939

4040
const result = await urlToAscii(
4141
attachment.proxyURL,
42-
attachment.width,
43-
attachment.height,
4442
// 2000 is the maximus size allowd by discord
4543
// -6 is to make space for the ``` before and after the message
4644
2000 - 6

src/util/imageToAscii.mjs

+27-69
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,70 @@
1-
import { pipeline } from 'stream/promises';
21
import got from 'got';
32
import sharp from 'sharp';
4-
import { PassThrough } from 'stream';
53

64
// TODO: expand the caracter list
75
const chars = '%&#MHGw*+-. ';
86
// const chars = 'BS#&@$%*!:. ';
97

108
/**
11-
* Calculate the maximum rectangle that has a surface area <= `maxSurface` and the same aspect ratio
12-
*
139
* @param {number} width the image width
1410
* @param {number} height the image heigth
15-
* @param {number} maxSurface maximum surface area of the output rectangle
11+
* @param {number} maxChars maximum character to use for the ASCII image
1612
* @returns
1713
*/
18-
const calculateSize = (width, height, maxSurface = 2000) => {
14+
const calculateSize = (width, height, maxChars = 2000) => {
1915
const imageArea = width * height;
2016

2117
/*
2218
We want to keep the aspect ratio of the image, this means that we must scale width and height by the same factor:
2319
(1) newW = scaleFactor * width
2420
(2) newH = scaleFactor * height
2521
26-
We also want the are of the new image to be <= than maxSurface (we add 1 to width to account for the \n characters at the end of the lines)
27-
(3) (newW + 1) * newH <= maxSurface
22+
We also want the are of the new image to be <= than maxChars (we add 1 to width to account for the \n characters at the end of the lines)
23+
(3) (newW + 1) * newH <= maxChars
2824
2925
We substitute (1) and (2) into (3) and solve for `scaleFactor` to find the formula below.
3026
*/
3127
const scaleFactor =
32-
(-height + Math.sqrt(height * height + 4 * imageArea * maxSurface)) /
28+
(-height + Math.sqrt(height * height + 4 * imageArea * maxChars)) /
3329
(2 * imageArea);
3430

3531
const newW = Math.floor(scaleFactor * width);
3632
const newH = Math.floor(scaleFactor * height);
3733
return [newW, newH];
3834
};
3935

40-
function toAscii(alphabet) {
41-
return async function* (source) {
42-
for await (const chunk of source) {
43-
let string = '';
36+
function bufferToAscii(buffer, alphabet, width) {
37+
let asciiImage = '';
38+
let i = 0;
4439

45-
for (const byte of chunk) {
46-
const index = Math.floor((byte / 255) * (alphabet.length - 1));
47-
string += alphabet[index];
48-
}
40+
for (const byte of buffer) {
41+
const index = Math.floor((byte / 255) * (alphabet.length - 1));
42+
asciiImage += alphabet[index];
4943

50-
yield string;
44+
i++;
45+
if (i >= width) {
46+
asciiImage += '\n';
47+
i = 0;
5148
}
52-
};
53-
}
54-
55-
function groupLines(lineLength) {
56-
return async function* (source) {
57-
let line = '';
58-
59-
for await (const string of source) {
60-
let start = 0;
61-
let end = lineLength - line.length;
62-
63-
while (start < string.length) {
64-
const piece = string.substring(start, end);
65-
line += piece;
66-
67-
start += piece.length;
68-
end += piece.length;
49+
}
6950

70-
if (line.length >= lineLength) {
71-
yield line + '\n';
72-
line = '';
73-
}
74-
}
75-
}
76-
};
51+
return asciiImage;
7752
}
7853

79-
export const urlToAscii = async (
80-
url,
81-
originalWidth,
82-
originalHeight,
83-
maxChars
84-
) => {
54+
export const urlToAscii = async (url, maxChars) => {
55+
const imageResponse = await got(url, { responseType: 'buffer' });
56+
const meta = await sharp(imageResponse.body).metadata();
57+
8558
const [width, height] = calculateSize(
86-
originalWidth,
59+
meta.width,
8760

8861
// Since pixels are square but character are not, we must squish the image so that it doesn't look stretched after being converted to ASCII
8962
// TODO: calculate an accurate value, this value was just eyeballed
90-
originalHeight * 0.4,
63+
meta.height * 0.4,
9164
maxChars
9265
);
9366

94-
const imageTransformer = sharp()
67+
const transformedBuffer = await sharp(imageResponse.body)
9568
.resize({
9669
width,
9770
height,
@@ -101,23 +74,8 @@ export const urlToAscii = async (
10174
.normalise()
10275
.sharpen() // makes the edges look a bit better
10376
.toColorspace('b-w')
104-
.raw();
105-
106-
const destination = new PassThrough();
107-
108-
const promise = pipeline(
109-
got.stream(url),
110-
imageTransformer,
111-
toAscii(chars),
112-
groupLines(width),
113-
destination
114-
);
77+
.raw()
78+
.toBuffer();
11579

116-
let asciiImage = '';
117-
for await (const line of destination) {
118-
asciiImage += line;
119-
}
120-
121-
await promise;
122-
return asciiImage;
80+
return bufferToAscii(transformedBuffer, chars, width);
12381
};

0 commit comments

Comments
 (0)