charify 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +155 -68
- package/package.json +14 -9
- package/src/charify.js +65 -0
- package/src/cli.js +79 -0
- package/src/constants.js +13 -0
- package/src/errors.js +8 -0
- package/src/index.js +7 -0
- package/src/utils.js +38 -0
- package/assets/charify.svg +0 -12
- package/bin/charify.js +0 -205
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CrazyBrain Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,146 +1,233 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# charify
|
|
4
|
+
|
|
5
|
+
High-quality image to ASCII art converter – CLI tool and npm library.
|
|
6
|
+
|
|
7
|
+
Powered by `sharp` for excellent image processing. No external Unix tools required.
|
|
5
8
|
|
|
6
9
|
---
|
|
7
10
|
|
|
8
11
|
## Install
|
|
9
12
|
|
|
10
|
-
Global install:
|
|
13
|
+
Global install (recommended for CLI usage):
|
|
11
14
|
|
|
12
|
-
```
|
|
15
|
+
```batch
|
|
13
16
|
npm install -g charify
|
|
14
17
|
```
|
|
15
18
|
|
|
16
19
|
Run without installing:
|
|
17
20
|
|
|
18
|
-
```
|
|
21
|
+
```batch
|
|
19
22
|
npx charify image.png
|
|
20
23
|
```
|
|
21
24
|
|
|
25
|
+
Install as a library in your project:
|
|
26
|
+
|
|
27
|
+
```batch
|
|
28
|
+
npm install charify
|
|
29
|
+
```
|
|
30
|
+
|
|
22
31
|
---
|
|
23
32
|
|
|
24
|
-
## Usage
|
|
33
|
+
## CLI Usage
|
|
25
34
|
|
|
26
|
-
```
|
|
27
|
-
charify image
|
|
28
|
-
charify image
|
|
29
|
-
charify image.png -o output.txt
|
|
30
|
-
charify image.png --html -o output.html
|
|
31
|
-
charify --link https://example.com/image.jpg
|
|
35
|
+
```batch
|
|
36
|
+
charify <image> [options]
|
|
37
|
+
charify --url <image-url> [options]
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
## Examples
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
### Basic conversion (prints to terminal)
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| --ratio | Height/width ratio (default: 0.55) |
|
|
42
|
-
| --gamma | Gamma correction (default: 2.2) |
|
|
43
|
-
| --invert | Invert brightness |
|
|
44
|
-
| --html | Export as HTML (only works if output file ends with .html) |
|
|
45
|
-
| -o, --output | Output file (.txt or .html) |
|
|
46
|
-
| --link | Load image from URL |
|
|
47
|
-
| --charset | Custom ASCII charset |
|
|
48
|
-
| --preset | Use a preset: dense, light, blocks, minimal |
|
|
44
|
+
```batch
|
|
45
|
+
charify photo.jpg
|
|
46
|
+
```
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
### Wider output
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
```batch
|
|
51
|
+
charify photo.jpg -w 140
|
|
52
|
+
```
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
| ------- | ---------------------- |
|
|
56
|
-
| dense | Best quality (default) |
|
|
57
|
-
| light | Softer output |
|
|
58
|
-
| blocks | Block-style ASCII |
|
|
59
|
-
| minimal | Very simple |
|
|
54
|
+
### Save to file
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
```batch
|
|
57
|
+
charify photo.jpg -o art.txt
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Export as standalone HTML
|
|
62
61
|
|
|
63
|
-
```
|
|
64
|
-
charify
|
|
62
|
+
```batch
|
|
63
|
+
charify photo.jpg --html -o art.html
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
### From URL
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
```batch
|
|
69
|
+
charify --url https://example.com/photo.jpg --preset blocks -o blocks.html
|
|
70
|
+
```
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
### Invert colors with custom preset
|
|
72
73
|
|
|
73
|
-
```
|
|
74
|
-
charify
|
|
74
|
+
```batch
|
|
75
|
+
charify photo.jpg --invert --preset light
|
|
75
76
|
```
|
|
76
77
|
|
|
78
|
+
### CLI Options
|
|
79
|
+
|
|
80
|
+
| Option | Description |
|
|
81
|
+
| --------------------- | ---------------------------------------------------------- |
|
|
82
|
+
| `<image>` | Path to local image file |
|
|
83
|
+
| `-u, --url <url>` | Fetch image from URL |
|
|
84
|
+
| `-w, --width <n>` | Output width in characters (default: 100) |
|
|
85
|
+
| `-r, --ratio <n>` | Height-to-width ratio for character aspect (default: 0.55) |
|
|
86
|
+
| `-g, --gamma <n>` | Gamma correction (default: 2.2) |
|
|
87
|
+
| `-p, --preset <name>` | Preset charset: dense (default), light, blocks, minimal |
|
|
88
|
+
| `-c, --charset <str>` | Custom character ramp (e.g. " .:-=+\*#%@") |
|
|
89
|
+
| `-i, --invert` | Invert brightness mapping |
|
|
90
|
+
| `--html` | Output as standalone HTML page |
|
|
91
|
+
| `-o, --output <file>` | Write result to file (.txt for plain, .html for HTML) |
|
|
92
|
+
|
|
93
|
+
Note: --html is automatically enabled if the output filename ends with .html.
|
|
94
|
+
|
|
77
95
|
---
|
|
78
96
|
|
|
79
|
-
##
|
|
97
|
+
## Library Usage
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
Import and use directly in Node.js projects:
|
|
82
100
|
|
|
83
|
-
```
|
|
84
|
-
charify
|
|
101
|
+
```js
|
|
102
|
+
import charify, { PRESETS, wrapAsHtml } from "charify";
|
|
103
|
+
import fs from "fs";
|
|
104
|
+
|
|
105
|
+
const buffer = fs.readFileSync("photo.jpg");
|
|
106
|
+
|
|
107
|
+
const ascii = await charify(buffer, {
|
|
108
|
+
width: 140,
|
|
109
|
+
preset: "blocks",
|
|
110
|
+
invert: true,
|
|
111
|
+
gamma: 2.0,
|
|
112
|
+
ratio: 0.6,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log(ascii);
|
|
116
|
+
|
|
117
|
+
// Or generate HTML manually
|
|
118
|
+
const html = wrapAsHtml(ascii);
|
|
119
|
+
fs.writeFileSync("art.html", html);
|
|
85
120
|
```
|
|
86
121
|
|
|
87
|
-
|
|
122
|
+
### Exported API
|
|
123
|
+
|
|
124
|
+
- `charify(buffer, options?)` – main function (default export)
|
|
125
|
+
- `PRESETS` – object with built-in charsets (dense, light, blocks, minimal)
|
|
126
|
+
- `wrapAsHtml(asciiString)` – utility to wrap plain ASCII in a minimal standalone HTML page
|
|
127
|
+
|
|
128
|
+
Requires sharp at runtime (peer dependency). Install it in your project:
|
|
129
|
+
|
|
130
|
+
```batch
|
|
131
|
+
npm install sharp
|
|
132
|
+
```
|
|
88
133
|
|
|
89
134
|
---
|
|
90
135
|
|
|
91
|
-
##
|
|
136
|
+
## Presets
|
|
137
|
+
|
|
138
|
+
| Preset | Charset | Best for |
|
|
139
|
+
| ------- | -------------- | ---------------------------- |
|
|
140
|
+
| dense | █▓▒░@%#\*+=-:. | Highest detail (default) |
|
|
141
|
+
| light | #\*+=-:. | Softer, less dense output |
|
|
142
|
+
| blocks | █▓▒░ | Classic blocky ASCII style |
|
|
143
|
+
| minimal | #.+ | Extremely simple, clean look |
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
Example:
|
|
94
146
|
|
|
95
|
-
```
|
|
96
|
-
charify --
|
|
147
|
+
```batch
|
|
148
|
+
charify photo.jpg --preset blocks
|
|
97
149
|
```
|
|
98
150
|
|
|
99
|
-
|
|
151
|
+
### Custom Charset
|
|
152
|
+
|
|
153
|
+
Override with your own ramp (dark → bright):
|
|
100
154
|
|
|
101
|
-
```
|
|
102
|
-
charify
|
|
155
|
+
```batch
|
|
156
|
+
charify photo.jpg --charset " .:-=+\*#%@"
|
|
103
157
|
```
|
|
104
158
|
|
|
105
159
|
---
|
|
106
160
|
|
|
107
|
-
##
|
|
161
|
+
## HTML Export
|
|
162
|
+
|
|
163
|
+
Creates a ready-to-open HTML file with green-on-black terminal styling:
|
|
164
|
+
|
|
165
|
+
```batch
|
|
166
|
+
charify photo.jpg --html -o my-art.html
|
|
167
|
+
```
|
|
108
168
|
|
|
109
|
-
|
|
169
|
+
# or simply
|
|
110
170
|
|
|
111
|
-
```
|
|
112
|
-
charify
|
|
171
|
+
```batch
|
|
172
|
+
charify photo.jpg -o my-art.html # auto-detects .html extension
|
|
113
173
|
```
|
|
114
174
|
|
|
115
175
|
---
|
|
116
176
|
|
|
117
|
-
##
|
|
177
|
+
## Browser Demo
|
|
118
178
|
|
|
119
|
-
|
|
179
|
+
Try charify instantly in the browser!
|
|
120
180
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
181
|
+
Open `demo/index.html` (included in the package):
|
|
182
|
+
|
|
183
|
+
- Drag & drop images
|
|
184
|
+
- Live preview
|
|
185
|
+
- Adjustable width, preset, invert
|
|
186
|
+
- Download as HTML
|
|
187
|
+
|
|
188
|
+
No build step required – uses Tailwind CSS via CDN.
|
|
124
189
|
|
|
125
190
|
---
|
|
126
191
|
|
|
127
192
|
## Local Development
|
|
128
193
|
|
|
129
|
-
|
|
194
|
+
```batch
|
|
195
|
+
git clone https://github.com/abdodev/charify.git
|
|
196
|
+
cd charify
|
|
197
|
+
npm install
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
# Run CLI directly
|
|
201
|
+
|
|
202
|
+
```batch
|
|
203
|
+
node src/cli.js image.png
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
# Test library
|
|
130
207
|
|
|
131
|
-
```
|
|
132
|
-
node
|
|
208
|
+
```batch
|
|
209
|
+
node -e "import { charify } from './src/index.js'; /_ your test code _/"
|
|
133
210
|
```
|
|
134
211
|
|
|
135
212
|
---
|
|
136
213
|
|
|
214
|
+
## ⭐ Star on GitHub
|
|
215
|
+
|
|
216
|
+
If you like charify, please star the repo!
|
|
217
|
+
|
|
218
|
+
[](https://github.com/abdodev/charify)
|
|
219
|
+
|
|
220
|
+
It helps others discover the project.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
137
224
|
## Donate
|
|
138
225
|
|
|
139
|
-
If
|
|
226
|
+
If charify saves you time or brings you joy, consider supporting development:
|
|
140
227
|
|
|
141
|
-
[](https://paypal.me/Abdoelsayd81)
|
|
228
|
+
[](https://paypal.me/Abdoelsayd81)
|
|
142
229
|
|
|
143
|
-
Thank you
|
|
230
|
+
Thank you ❤️
|
|
144
231
|
|
|
145
232
|
---
|
|
146
233
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "charify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "High-quality image to ASCII art CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ascii",
|
|
@@ -18,19 +18,24 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"author": "AbdoDev",
|
|
20
20
|
"type": "module",
|
|
21
|
-
"main": "/bin/charify.js",
|
|
22
21
|
"bin": {
|
|
23
|
-
"charify": "
|
|
22
|
+
"charify": "./src/cli.js"
|
|
24
23
|
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
25
27
|
"scripts": {
|
|
26
|
-
"
|
|
28
|
+
"start": "node src/cli.js"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
|
-
"commander": "^12.
|
|
30
|
-
|
|
31
|
+
"commander": "^12.1.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"sharp": "^0.33.5"
|
|
31
35
|
},
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"sharp": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
}
|
package/src/charify.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sharp from "sharp";
|
|
2
|
+
import { PRESETS, DEFAULTS } from "./constants.js";
|
|
3
|
+
import { SharpMissingError } from "./errors.js";
|
|
4
|
+
|
|
5
|
+
export async function convertToAscii(buffer, options = {}) {
|
|
6
|
+
if (!sharp) {
|
|
7
|
+
throw new SharpMissingError();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
width = DEFAULTS.width,
|
|
12
|
+
ratio = DEFAULTS.ratio,
|
|
13
|
+
gamma = DEFAULTS.gamma,
|
|
14
|
+
preset = DEFAULTS.preset,
|
|
15
|
+
charset,
|
|
16
|
+
invert = false,
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
const chars = charset || PRESETS[preset] || PRESETS.dense;
|
|
20
|
+
const rampLength = chars.length - 1;
|
|
21
|
+
|
|
22
|
+
let image = sharp(buffer)
|
|
23
|
+
.resize({ width, fit: "inside", withoutEnlargement: true })
|
|
24
|
+
.grayscale();
|
|
25
|
+
|
|
26
|
+
const { height: originalHeight } = await image.metadata();
|
|
27
|
+
const targetHeight = Math.round(originalHeight * ratio);
|
|
28
|
+
|
|
29
|
+
image = image.resize(width, targetHeight);
|
|
30
|
+
|
|
31
|
+
const { data, info } = await image
|
|
32
|
+
.raw()
|
|
33
|
+
.toBuffer({ resolveWithObject: true });
|
|
34
|
+
|
|
35
|
+
// Normalize contrast
|
|
36
|
+
let min = 255;
|
|
37
|
+
let max = 0;
|
|
38
|
+
for (const value of data) {
|
|
39
|
+
if (value < min) min = value;
|
|
40
|
+
if (value > max) max = value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const range = max - min || 1;
|
|
44
|
+
const normalized = Buffer.from(
|
|
45
|
+
data.map((v) => Math.round(((v - min) / range) * 255))
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Build ASCII
|
|
49
|
+
let result = "";
|
|
50
|
+
for (let y = 0; y < info.height; y++) {
|
|
51
|
+
for (let x = 0; x < info.width; x++) {
|
|
52
|
+
const idx = y * info.width + x;
|
|
53
|
+
let intensity = normalized[idx];
|
|
54
|
+
intensity = Math.pow(intensity / 255, 1 / gamma) * 255;
|
|
55
|
+
|
|
56
|
+
const charIndex = Math.floor((intensity / 255) * rampLength);
|
|
57
|
+
const finalIndex = invert ? rampLength - charIndex : charIndex;
|
|
58
|
+
|
|
59
|
+
result += chars[finalIndex];
|
|
60
|
+
}
|
|
61
|
+
result += "\n";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { charify, PRESETS, wrapAsHtml } from "./index.js";
|
|
7
|
+
import { fetchImage } from "./utils.js";
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("charify")
|
|
13
|
+
.description("Convert images to ASCII art")
|
|
14
|
+
.argument("[image]", "path to image file")
|
|
15
|
+
.option("-u, --url <url>", "fetch image from URL")
|
|
16
|
+
.option("-w, --width <number>", "output width (characters)", parseInt)
|
|
17
|
+
.option("-r, --ratio <number>", "height scaling ratio", parseFloat)
|
|
18
|
+
.option("-g, --gamma <number>", "gamma correction", parseFloat)
|
|
19
|
+
.option(
|
|
20
|
+
"-p, --preset <name>",
|
|
21
|
+
"character preset: dense, light, blocks, minimal"
|
|
22
|
+
)
|
|
23
|
+
.option("-c, --charset <string>", "custom character ramp")
|
|
24
|
+
.option("-i, --invert", "invert brightness")
|
|
25
|
+
.option("--html", "output as standalone HTML")
|
|
26
|
+
.option("-o, --output <file>", "write to file instead of stdout")
|
|
27
|
+
.addHelpText(
|
|
28
|
+
"before",
|
|
29
|
+
`
|
|
30
|
+
_ _ __
|
|
31
|
+
___| |__ __ _ _ __(_)/ _|_ _
|
|
32
|
+
/ __| '_ \\ / _' | '__| | |_| | | |
|
|
33
|
+
| (__| | | | (_| | | | | _| |_| |
|
|
34
|
+
\\___|_| |_|\\__,_|_| |_|_| \\__, |
|
|
35
|
+
|___/
|
|
36
|
+
`
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
program.parse();
|
|
40
|
+
|
|
41
|
+
const opts = program.opts();
|
|
42
|
+
const inputPath = program.args[0];
|
|
43
|
+
|
|
44
|
+
if (!inputPath && !opts.url) {
|
|
45
|
+
console.error("Error: provide an image path or --url");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
(async () => {
|
|
50
|
+
try {
|
|
51
|
+
const buffer = opts.url
|
|
52
|
+
? await fetchImage(opts.url)
|
|
53
|
+
: fs.readFileSync(path.resolve(inputPath));
|
|
54
|
+
|
|
55
|
+
const ascii = await charify(buffer, {
|
|
56
|
+
width: opts.width,
|
|
57
|
+
ratio: opts.ratio,
|
|
58
|
+
gamma: opts.gamma,
|
|
59
|
+
preset: opts.preset,
|
|
60
|
+
charset: opts.charset,
|
|
61
|
+
invert: opts.invert,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const output =
|
|
65
|
+
opts.html || (opts.output && opts.output.endsWith(".html"))
|
|
66
|
+
? wrapAsHtml(ascii)
|
|
67
|
+
: ascii;
|
|
68
|
+
|
|
69
|
+
if (opts.output) {
|
|
70
|
+
fs.writeFileSync(opts.output, output, "utf8");
|
|
71
|
+
console.log(`Saved to ${opts.output}`);
|
|
72
|
+
} else {
|
|
73
|
+
process.stdout.write(output);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`Error: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
})();
|
package/src/constants.js
ADDED
package/src/errors.js
ADDED
package/src/index.js
ADDED
package/src/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import https from "https";
|
|
2
|
+
|
|
3
|
+
export function fetchImage(url) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
https
|
|
6
|
+
.get(url, (res) => {
|
|
7
|
+
if (res.statusCode !== 200) {
|
|
8
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const chunks = [];
|
|
12
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
13
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
14
|
+
})
|
|
15
|
+
.on("error", reject);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function wrapAsHtml(ascii) {
|
|
20
|
+
return `<!DOCTYPE html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="utf-8">
|
|
24
|
+
<title>ASCII Art</title>
|
|
25
|
+
<style>
|
|
26
|
+
body {
|
|
27
|
+
margin: 20px;
|
|
28
|
+
background: #000;
|
|
29
|
+
color: #0f0;
|
|
30
|
+
font-family: monospace;
|
|
31
|
+
white-space: pre;
|
|
32
|
+
line-height: 1;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body>${ascii}</body>
|
|
37
|
+
</html>`;
|
|
38
|
+
}
|
package/assets/charify.svg
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
3
|
-
<svg width="100%" height="100%" viewBox="0 0 512 127" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
4
|
-
<g transform="matrix(1,0,0,1,0,-6.755875)">
|
|
5
|
-
<g transform="matrix(1,-0,-0,1,-0,6.755875)">
|
|
6
|
-
<use xlink:href="#_Image1" x="0" y="0" width="512px" height="127px"/>
|
|
7
|
-
</g>
|
|
8
|
-
</g>
|
|
9
|
-
<defs>
|
|
10
|
-
<image id="_Image1" width="512px" height="127px" xlink:href="data:image/png;base64,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"/>
|
|
11
|
-
</defs>
|
|
12
|
-
</svg>
|
package/bin/charify.js
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import https from "https";
|
|
6
|
-
import sharp from "sharp";
|
|
7
|
-
import { Command } from "commander";
|
|
8
|
-
|
|
9
|
-
/* ================= CONFIG ================= */
|
|
10
|
-
|
|
11
|
-
const PRESETS = {
|
|
12
|
-
dense: "█▓▒░@%#*+=-:. ",
|
|
13
|
-
light: "#*+=-:. ",
|
|
14
|
-
blocks: "█▓▒░ ",
|
|
15
|
-
minimal: "#.+ ",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const DEFAULT_PRESET = "dense";
|
|
19
|
-
const DEFAULT_WIDTH = 100;
|
|
20
|
-
const DEFAULT_RATIO = 0.55;
|
|
21
|
-
const DEFAULT_GAMMA = 2.2;
|
|
22
|
-
|
|
23
|
-
/* ================= CLI ================= */
|
|
24
|
-
|
|
25
|
-
const program = new Command();
|
|
26
|
-
|
|
27
|
-
program
|
|
28
|
-
.name("charify")
|
|
29
|
-
.description("Convert images to ASCII art")
|
|
30
|
-
.argument("[image]", "local image path")
|
|
31
|
-
.option("--link <url>", "image URL")
|
|
32
|
-
.option("-w, --width <number>", "output width", String(DEFAULT_WIDTH))
|
|
33
|
-
.option("--ratio <number>", "height/width ratio", String(DEFAULT_RATIO))
|
|
34
|
-
.option("--gamma <number>", "gamma correction", String(DEFAULT_GAMMA))
|
|
35
|
-
.option("--charset <chars>", "custom ASCII charset")
|
|
36
|
-
.option("--preset <name>", "preset: dense | light | blocks | minimal")
|
|
37
|
-
.option("--invert", "invert brightness")
|
|
38
|
-
.option(
|
|
39
|
-
"--html",
|
|
40
|
-
"export as HTML (only works if output file ends with .html)"
|
|
41
|
-
)
|
|
42
|
-
.option("-o, --output <file>", "output file (.txt or .html)");
|
|
43
|
-
program
|
|
44
|
-
.addHelpText(
|
|
45
|
-
"before",
|
|
46
|
-
`
|
|
47
|
-
_ _ __
|
|
48
|
-
___| |__ __ _ _ __(_)/ _|_ _
|
|
49
|
-
/ __| '_ \\ / _' | '__| | |_| | | |
|
|
50
|
-
| (__| | | | (_| | | | | _| |_| |
|
|
51
|
-
\\___|_| |_|\\__,_|_| |_|_| \\__, |
|
|
52
|
-
|___/
|
|
53
|
-
`
|
|
54
|
-
)
|
|
55
|
-
.parse(process.argv);
|
|
56
|
-
|
|
57
|
-
const opts = program.opts();
|
|
58
|
-
const inputPath = program.args[0];
|
|
59
|
-
|
|
60
|
-
if (!opts.link && !inputPath) {
|
|
61
|
-
console.error("❌ Provide an image path or --link URL");
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* ================= HELPERS ================= */
|
|
66
|
-
|
|
67
|
-
function fetchImage(url) {
|
|
68
|
-
return new Promise((resolve, reject) => {
|
|
69
|
-
https.get(url, (res) => {
|
|
70
|
-
const chunks = [];
|
|
71
|
-
res.on("data", (d) => chunks.push(d));
|
|
72
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
73
|
-
res.on("error", reject);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function gammaCorrect(v, gamma) {
|
|
79
|
-
return Math.pow(v / 255, 1 / gamma) * 255;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function normalizeBuffer(buffer) {
|
|
83
|
-
let min = 255,
|
|
84
|
-
max = 0;
|
|
85
|
-
for (const v of buffer) {
|
|
86
|
-
if (v < min) min = v;
|
|
87
|
-
if (v > max) max = v;
|
|
88
|
-
}
|
|
89
|
-
if (max === min) return buffer;
|
|
90
|
-
|
|
91
|
-
return Buffer.from(
|
|
92
|
-
buffer.map((v) => Math.round(((v - min) / (max - min)) * 255))
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function toAscii(buffer, width, charset, invert, gamma) {
|
|
97
|
-
let out = "";
|
|
98
|
-
const len = charset.length - 1;
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < buffer.length; i += width) {
|
|
101
|
-
for (let x = 0; x < width; x++) {
|
|
102
|
-
let v = buffer[i + x];
|
|
103
|
-
v = gammaCorrect(v, gamma);
|
|
104
|
-
const idx = Math.floor((v / 255) * len);
|
|
105
|
-
out += invert ? charset[len - idx] : charset[idx];
|
|
106
|
-
}
|
|
107
|
-
out += "\n";
|
|
108
|
-
}
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function wrapHtml(ascii) {
|
|
113
|
-
return `<!doctype html>
|
|
114
|
-
<html>
|
|
115
|
-
<head>
|
|
116
|
-
<meta charset="utf-8">
|
|
117
|
-
<title>charify</title>
|
|
118
|
-
<style>
|
|
119
|
-
body {
|
|
120
|
-
background: #000;
|
|
121
|
-
color: #0f0;
|
|
122
|
-
font-family: monospace;
|
|
123
|
-
white-space: pre;
|
|
124
|
-
line-height: 1;
|
|
125
|
-
}
|
|
126
|
-
</style>
|
|
127
|
-
</head>
|
|
128
|
-
<body>${ascii}</body>
|
|
129
|
-
</html>`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/* ================= MAIN ================= */
|
|
133
|
-
|
|
134
|
-
(async () => {
|
|
135
|
-
try {
|
|
136
|
-
const width = Number(opts.width);
|
|
137
|
-
const ratio = Number(opts.ratio);
|
|
138
|
-
const gamma = Number(opts.gamma);
|
|
139
|
-
|
|
140
|
-
const charset =
|
|
141
|
-
opts.charset || PRESETS[opts.preset] || PRESETS[DEFAULT_PRESET];
|
|
142
|
-
|
|
143
|
-
const inputBuffer = opts.link
|
|
144
|
-
? await fetchImage(opts.link)
|
|
145
|
-
: fs.readFileSync(path.resolve(inputPath));
|
|
146
|
-
|
|
147
|
-
let sharpInstance = sharp(inputBuffer)
|
|
148
|
-
.resize({
|
|
149
|
-
width,
|
|
150
|
-
fit: sharp.fit.inside,
|
|
151
|
-
withoutEnlargement: true,
|
|
152
|
-
})
|
|
153
|
-
.grayscale();
|
|
154
|
-
|
|
155
|
-
const metadata = await sharpInstance.metadata();
|
|
156
|
-
|
|
157
|
-
const adjustedHeight = Math.round(metadata.height * ratio);
|
|
158
|
-
|
|
159
|
-
sharpInstance = sharpInstance.resize(width, adjustedHeight);
|
|
160
|
-
|
|
161
|
-
const { data, info } = await sharpInstance
|
|
162
|
-
.raw()
|
|
163
|
-
.toBuffer({ resolveWithObject: true });
|
|
164
|
-
|
|
165
|
-
const normalizedData = normalizeBuffer(data);
|
|
166
|
-
|
|
167
|
-
const ascii = toAscii(
|
|
168
|
-
normalizedData,
|
|
169
|
-
info.width,
|
|
170
|
-
charset,
|
|
171
|
-
opts.invert,
|
|
172
|
-
gamma
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
let output = ascii;
|
|
176
|
-
const wantHtml =
|
|
177
|
-
opts.html && opts.output && opts.output.toLowerCase().endsWith(".html");
|
|
178
|
-
|
|
179
|
-
if (wantHtml) {
|
|
180
|
-
output = wrapHtml(ascii);
|
|
181
|
-
} else if (
|
|
182
|
-
opts.html &&
|
|
183
|
-
(!opts.output || !opts.output.toLowerCase().endsWith(".html"))
|
|
184
|
-
) {
|
|
185
|
-
if (!opts.output) {
|
|
186
|
-
output = ascii;
|
|
187
|
-
} else {
|
|
188
|
-
console.warn(
|
|
189
|
-
"⚠ Warning: --html ignored because output file extension is not .html"
|
|
190
|
-
);
|
|
191
|
-
output = ascii;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (opts.output) {
|
|
196
|
-
fs.writeFileSync(opts.output, output, "utf8");
|
|
197
|
-
console.log("✔ Saved:", opts.output);
|
|
198
|
-
} else {
|
|
199
|
-
process.stdout.write(output);
|
|
200
|
-
}
|
|
201
|
-
} catch (err) {
|
|
202
|
-
console.error("❌ Error:", err.message);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
})();
|