1
- import { pipeline } from 'stream/promises' ;
2
1
import got from 'got' ;
3
2
import sharp from 'sharp' ;
4
- import { PassThrough } from 'stream' ;
5
3
6
4
// TODO: expand the caracter list
7
5
const chars = '%&#MHGw*+-. ' ;
8
6
// const chars = 'BS#&@$%*!:. ';
9
7
10
8
/**
11
- * Calculate the maximum rectangle that has a surface area <= `maxSurface` and the same aspect ratio
12
- *
13
9
* @param {number } width the image width
14
10
* @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
16
12
* @returns
17
13
*/
18
- const calculateSize = ( width , height , maxSurface = 2000 ) => {
14
+ const calculateSize = ( width , height , maxChars = 2000 ) => {
19
15
const imageArea = width * height ;
20
16
21
17
/*
22
18
We want to keep the aspect ratio of the image, this means that we must scale width and height by the same factor:
23
19
(1) newW = scaleFactor * width
24
20
(2) newH = scaleFactor * height
25
21
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
28
24
29
25
We substitute (1) and (2) into (3) and solve for `scaleFactor` to find the formula below.
30
26
*/
31
27
const scaleFactor =
32
- ( - height + Math . sqrt ( height * height + 4 * imageArea * maxSurface ) ) /
28
+ ( - height + Math . sqrt ( height * height + 4 * imageArea * maxChars ) ) /
33
29
( 2 * imageArea ) ;
34
30
35
31
const newW = Math . floor ( scaleFactor * width ) ;
36
32
const newH = Math . floor ( scaleFactor * height ) ;
37
33
return [ newW , newH ] ;
38
34
} ;
39
35
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 ;
44
39
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 ] ;
49
43
50
- yield string ;
44
+ i ++ ;
45
+ if ( i >= width ) {
46
+ asciiImage += '\n' ;
47
+ i = 0 ;
51
48
}
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
+ }
69
50
70
- if ( line . length >= lineLength ) {
71
- yield line + '\n' ;
72
- line = '' ;
73
- }
74
- }
75
- }
76
- } ;
51
+ return asciiImage ;
77
52
}
78
53
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
+
85
58
const [ width , height ] = calculateSize (
86
- originalWidth ,
59
+ meta . width ,
87
60
88
61
// 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
89
62
// TODO: calculate an accurate value, this value was just eyeballed
90
- originalHeight * 0.4 ,
63
+ meta . height * 0.4 ,
91
64
maxChars
92
65
) ;
93
66
94
- const imageTransformer = sharp ( )
67
+ const transformedBuffer = await sharp ( imageResponse . body )
95
68
. resize ( {
96
69
width,
97
70
height,
@@ -101,23 +74,8 @@ export const urlToAscii = async (
101
74
. normalise ( )
102
75
. sharpen ( ) // makes the edges look a bit better
103
76
. 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 ( ) ;
115
79
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 ) ;
123
81
} ;
0 commit comments