charify 1.0.0 → 2.0.1

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 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
  ![charify logo](./assets/charify.svg)
2
2
 
3
- High-quality image to ASCII art converter for the command line.
4
- npm-ready. No dependencies on Unix tools.
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
- ```bash
15
+ ```batch
13
16
  npm install -g charify
14
17
  ```
15
18
 
16
19
  Run without installing:
17
20
 
18
- ```bash
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
- ```bash
27
- charify image.png
28
- charify image.jpg -w 120
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
- ## Options
42
+ ### Basic conversion (prints to terminal)
37
43
 
38
- | Option | Description |
39
- | ------------ | ---------------------------------------------------------- |
40
- | -w, --width | Output width (default: 100) |
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
- ## Presets
50
+ ```batch
51
+ charify photo.jpg -w 140
52
+ ```
53
53
 
54
- | Name | Description |
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
- Example:
56
+ ```batch
57
+ charify photo.jpg -o art.txt
58
+ ```
59
+
60
+ ### Export as standalone HTML
62
61
 
63
- ```bash
64
- charify image.png --preset blocks
62
+ ```batch
63
+ charify photo.jpg --html -o art.html
65
64
  ```
66
65
 
67
- ---
66
+ ### From URL
68
67
 
69
- ## Custom Charset
68
+ ```batch
69
+ charify --url https://example.com/photo.jpg --preset blocks -o blocks.html
70
+ ```
70
71
 
71
- Specify your own charset to control ASCII output characters:
72
+ ### Invert colors with custom preset
72
73
 
73
- ```bash
74
- charify image.png --charset " .:-=+*#%@"
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
- ## HTML Export
97
+ ## Library Usage
80
98
 
81
- To export ASCII art wrapped in an HTML page, use `--html` **with** an output file ending in `.html`:
99
+ Import and use directly in Node.js projects:
82
100
 
83
- ```bash
84
- charify image.png --html -o art.html
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
- If you use `--html` without specifying an output file ending with `.html`, it will be ignored and plain ASCII will be output instead.
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
- ## Input from URL
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
- Load image from the internet:
145
+ Example:
94
146
 
95
- ```bash
96
- charify --link https://example.com/image.jpg
147
+ ```batch
148
+ charify photo.jpg --preset blocks
97
149
  ```
98
150
 
99
- You can combine with other options like `--html` and `-o`:
151
+ ### Custom Charset
152
+
153
+ Override with your own ramp (dark → bright):
100
154
 
101
- ```bash
102
- charify --link https://example.com/image.jpg --html -o output.html
155
+ ```batch
156
+ charify photo.jpg --charset " .:-=+\*#%@"
103
157
  ```
104
158
 
105
159
  ---
106
160
 
107
- ## Quality Tuning
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
- Adjust gamma correction and character aspect ratio:
169
+ # or simply
110
170
 
111
- ```bash
112
- charify image.png --gamma 2.0 --ratio 0.6
171
+ ```batch
172
+ charify photo.jpg -o my-art.html # auto-detects .html extension
113
173
  ```
114
174
 
115
175
  ---
116
176
 
117
- ## Invert Brightness
177
+ ## Browser Demo
118
178
 
119
- Invert the brightness mapping:
179
+ Try charify instantly in the browser!
120
180
 
121
- ```bash
122
- charify image.png --invert
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
- Run directly without installing:
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
- ```bash
132
- node bin/charify.js image.png
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
+ [![GitHub stars](https://img.shields.io/github/stars/abdodev/charify?style=social)](https://github.com/abdodev/charify)
219
+
220
+ It helps others discover the project.
221
+
222
+ ---
223
+
137
224
  ## Donate
138
225
 
139
- If you find **charify** useful and want to support development, you can donate any amount here:
226
+ If charify saves you time or brings you joy, consider supporting development:
140
227
 
141
- [![Donate](https://img.shields.io/badge/Donate-00457C?logo=paypal)](https://paypal.me/Abdoelsayd81)
228
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-00457C?logo=paypal)](https://paypal.me/Abdoelsayd81)
142
229
 
143
- Thank you for your support!
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": "1.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "High-quality image to ASCII art CLI",
5
5
  "keywords": [
6
6
  "ascii",
@@ -18,19 +18,25 @@
18
18
  "license": "MIT",
19
19
  "author": "AbdoDev",
20
20
  "type": "module",
21
- "main": "/bin/charify.js",
21
+ "main": "src/index.js",
22
22
  "bin": {
23
- "charify": "bin/charify.js"
23
+ "charify": "./src/cli.js"
24
24
  },
25
+ "files": [
26
+ "src"
27
+ ],
25
28
  "scripts": {
26
- "test": "echo \"Error: no test specified\" && exit 1"
29
+ "start": "node src/cli.js"
27
30
  },
28
31
  "dependencies": {
29
- "commander": "^12.0.0",
30
- "sharp": "^0.33.0"
32
+ "commander": "^12.1.0"
33
+ },
34
+ "peerDependencies": {
35
+ "sharp": "^0.33.5"
31
36
  },
32
- "devDependencies": {},
33
- "engines": {
34
- "node": ">=18"
37
+ "peerDependenciesMeta": {
38
+ "sharp": {
39
+ "optional": true
40
+ }
35
41
  }
36
42
  }
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
+ })();
@@ -0,0 +1,13 @@
1
+ export const PRESETS = {
2
+ dense: "█▓▒░@%#*+=-:. ",
3
+ light: "#*+=-:. ",
4
+ blocks: "█▓▒░ ",
5
+ minimal: "#.+ ",
6
+ };
7
+
8
+ export const DEFAULTS = {
9
+ width: 100,
10
+ ratio: 0.55,
11
+ gamma: 2.2,
12
+ preset: "dense",
13
+ };
package/src/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ export class SharpMissingError extends Error {
2
+ constructor() {
3
+ super(
4
+ "sharp is required but not installed. Install it with: npm install sharp"
5
+ );
6
+ this.name = "SharpMissingError";
7
+ }
8
+ }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { convertToAscii as charify } from "./charify.js";
2
+ export { PRESETS } from "./constants.js";
3
+ export { wrapAsHtml } from "./utils.js";
4
+
5
+ export default function (buffer, options) {
6
+ return charify(buffer, options);
7
+ }
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
+ }
@@ -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,iVBORw0KGgoAAAANSUhEUgAAAgAAAAB/CAYAAACZk3rHAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOydd5wcZ33wv8/M7O41lZMsW5Kbim1ZllwwtmUbN2xsGQwGkwChkxCCgwMbeGnJG3gJeUkILxCO0FsIJBTZFGPAxAVXbMsF27IlW5JVrC6drt/etnme5/3j2dnb3WszW65Iz/c+87mbuSnPzs48z+/5VYHFYrFYRmcn5zHERUhWozmTHKtI04/PsyieJcez9HAHb2H/VDfVYomKmOoGWCwWy7Sjk7n4fIEcf0EOcAAPSAO7gSzgAj4wQD9pPkyG73ATeuoafQTwBAk07WjakbhohtCkEKSANBchp7qJRxJWALBYLJZSengtkq/jsAgX0EAMaCn8fwfQhxEC9hcWBTjcQ573kGTbVDR7RvAczWjWoLkUn3OQzEMzD5gHtAOtaMw9V4WlFE0GSCEZQLEL822YRbGDQXbwIvv4mxFHWkbBCgBRSXEOOe7nRUwHEAUJHAR28TuSvLH+jbNYLDUxyHVofg1AE+DBGZzBAQ6QI4evfWS+MAkVkD+YhweBQ8Uz7AfOIEnvpLd9OrKdueR4GZpLgcuA83CI4TA8wAfalWCbxAgAsrAeCAR5IIfRuuQK/3cKiyis7wIGuI0k10/aZ5zBeFPdgBnHIA4es1gOvMhICXU0AqVgJ+YBhebGNM5isVRNijnAN3GAZmijjWaa2Zrbilaa0ZT73nwPeY1EP6phCwCLgM8DfzmJLZ9+7GYpko8g+QtcEngUBaqiRiVWWE+YQ+LEmcUsfHxU4QdAF35yMofKKHROwyAwhBEGFEY4eBoYAODAJH7SGY0VAKLiYyTNNmhe1Uw2pBpAZzT6XmsetFimLZp/xeF4mszgr9H0ZHpGHfhLcZtd5BqJ7tNGwwfvpoMfk+TuSWj19GI3Z6L5OA5vIoFbHGESQAsIBEtZygADRqNSGOy10iil6Ff9RtgaBYHAdV1Ei4BWQIBwBY7jkDmYMZoYw76Gf84jBCsAVIMGcnBy/GS25bdN2EEA+IN+w5tlsViqpI8zUbyXGCCMAHA4czjUuw3gNrn4l/pwS3HTvwNnNKSt05FOVpHjXxFcR4zigI8Lp3IqAwyQJk1WZ9md3z08yFcxJ9J6+CDta6MpSJXtsrfqz3GU4Ux1A2YyefJoqUMt1iXFYpnG5LkACcThGq6hK9cVeXBy57iwori6kg5m17eR05Ru/hSH9TRzHbOAY4BZcKF7IXOYw878Tg5nDpPKpPAzvukPA7t+vSifX1kNQEisAFAD2kb8WCxHBprVgUv08zw/php6PIQQsKRs06o6tGz60otLN5/B4WbitDIHaIPzOI822ngi9wSpTArtN2DAL0VTKQBYDUBIrABgsVgsmtW45s9BBqs7h8A4ug2zurZGTWP6mYPmVlz+njjQBqc5pzGHOTyde5pMJjM8028wmoJT4DBWAxAS6wNgsVgsPmeQM39myVY/cJUHVh+ZGoBB5qH5Ax6nB979V3AFD6uHkTk5KYN+GRLYWVzLA4cnuQUzFqsBsFgslhw99AI5iuFnVVF+aHdtjZqGpBAI/gOH02kCmuBszuah/EPI7BQM/hhHwJLAv/0krcdVWKwAYLFYLJJnyQMpcANbQFQ0lcnBnq25XdOPvwWuD2L4l7GMTblNKH/qxlztl0kd1v4fASsAWCwWi+ZZBJAtCABV5EjVSps6AcMcWQJAijXA53CAmHH2253bbWz9U0l5dQBr/4+AFQAsFovF51l84CD0DfUZj/6IyH4Jm4qrWTiCagKkmAf8FIFHE1zJlWzwN9R38NegpUb5CpVXaL8QRj1GFsbiYVYDUDXWCdBisVj6uRfBAfpYSBbUOcoIASHHN5VTcF/Zpp+SPIIq1yn+GZeTaYIECR7hkZrU/lppdE6js+Y3eSCDseUfwvhSJDBJ01uBWZjEQnGG8/+7hd8DZae2GoAIWAHAYrFYLqCX+3kfmp/TBXKzxDvVM6aACYQAlVOo55Sp9WE4BHyooe2dTAY4BngnLiBgPvM5lDlUXRY/qVGDCv2ihvX1bihgNQCRsAKAxWKxAFzGL7iLm8nzBraCP+AjVgnchDvqYKelNmr/e6kMPLuJJF2T0ubJQPNeXJqIw9VczX35+yIP/loXBv5nNGwcd1cJdAE9mDl+AoJ4A5owJYTGw2oAImAFAIvFYgno5/0McDZZTmMA9AGNv8SHBUDMFKTRUht19Q5Kbf4B3yJZUhFgpjNAHM1NQWDEUzyFktFU/1pq5F4J/zPiX0PA/cDvgXuArUA/yXHEiw5cYD6wEDiu8Lt0eSFS445yrABgsVgsAa/nIN/kHDJ8kjQfoQ+XPcP/Hif9937gfST55SS0cvLQvBGHRSRMyN8euSfS7F9Ljdwq4YGyzQNAB/BvJCPmSjB+FYcKi6VGrABgsVgspbyXNPB3dHAL8GVgDYyZHGAA+CnwUZL0TFILJ4d+BIoPBkp3jUblw8/+tdLIF0YM/jcDN0Ye+C0NwQoAFovFMhpJngBeRgdNmNK+5xR+DwFPFZadR2zmOZ9TEZyLBx4eBzgQfvavQR6SRsE/zGeB/33E3q8ZiBUALBaLZTySZIA/FpajB81FCMCDC7mQ9dnwbvsyLeG2sk3/lySfqHMLLTViEwFZLBaLZSSai4Ip4k52onW46b9WGv1C2b7rgX+sd/MstWMFAIvFYrGMxkWB58NARbad8VBpVRrjnwXeThK/zm2z1AFrArBYLBZLOV3MAlbjQTPNZHXIEska9OGyHX9Mkq2NaaSlVqwGwGKxWCzlaC5A4ODC+ZyPzIXLaqzyqtLx7+uNaJ6lPlgBwGKxWCzlBA6ADuxlb3j7f6aQJMnwFPBYYxpoqQdWALBYLBZLOYoVgcp/kMHQh+lsmaBw17hZ/SxTzuT4ADyHwGc5PiehmYdmPjCvuGjmo5iLJINmABhAM4CkF8VWNJuA53k5Q5PS3unEZgRZFqBZimYJsBRYgs8S8igkh4FONJ34HCLPenw28app+uL1MJ9DzKYPiUICPib/92h/Ky6cpp/jaOF5mlGch8/FSI5H0Y6iHUkLPp1o9qPZT559ZLmfV/HiVDfZUgcU7cGblyUb7hgNpMu22Nn/NKcxAsDzCDRnAJcBl6O5DFhEUGLbwVTZChYw5SADM5MuWYKUEQLNXexEsh7Jb9DcznVHUMGNSnazihw3kudtwNxAHVe25KEspYbC3DPBPn7LHUjuYIjf8Cb6J7v5YxLn07TyPgYgVDqQR1CAj0aiGUTTC8WlB+hF04umB8kBJE8Dm7gqbK81hQzwag5zIV0wbuFYF2hmNor3kya0MxYDwGZ+h+T9JCPkSN9LjDx/is9f43MhDjFimGcueCczhTYHbQne19t4FM060tzCmyZBGBhgLV1cymHGv4eTRRZ4EkjyD1PdlBqZG/TNEhnqmdNaVxZFml4CwBBzyPER9mBSOUWdWgQCzgZ2keRbdW9fWFII0nyS3cTIVXG8wvScWxmqnwCwB48cbyDPG1BciuAYPEwtp3jhokGXHNRydjG1nVzMy5theFDzgVzJ0osgxVJgKTH+DAfFr3mYPDeT43u8KUKcynSmmznk+AqSt9GKuTf5whLcrzjmYUxh7mkwb05hMmQPsBiPd+HyLgTd/Ih/Js/XeGeFfD4VDLGAdkyd76DudyXBi6kAhYMiTh7I0YxmATAsOIqSY4ZfaJ+72USQrU3zFHme5JX01vvj1IRmLcfwNwxgvsfROqRCIhZmY2qh9cGEok3QUW0FJNcCFxOmSEo3ghzvxueTxDiRFsrL4ZYKoWnM8+Zjns0BoBvIcgEJLkDxOb7PF8nwf7ixgZo7zcuZz8cYwPQfU60vShX/mukCQHtgIJZhJStFaTHeFLCz3o2qiT76aeLPOInlbMc8t1GfF9ODZungRyQj2EbqiWY1TXyKJsz7F/Uz5KEQl3Fr7QLAHppQvAvNR4izjCZMh1XacTnQTjsuLnnyhc9Q/qNQxUIbotirm/1838fv9E2hxyHMi96NQ56X4fEyHP6RdXwDny/zlhlcDrKfs5DcRoKT8DCDpAvzmIeDg0QW75ePj2o39yy4X770yR/Kwy5gECPl9TAPl88T54N8h0+R4ntTmopTsoA8MA8Wz1tMH30jdgmeA4Uyn1MpVFaZ7z1HubFAMWw0yGK6nR48fM4ixlnEeEdhEJPcxh1o/gvFrbyupKueKlIYYe5MTMc5VqR0AlPnDGg/pp0cufGK0gAw1DkEDxVXl03Yll7mIPk2Md5QFNpbzLu4hCUMMECOHKrwo+dqM+MrvKr5bB7ZLs3nGAC6cFB8GI8b+Arv4W+4Z8I2VEMKIxSvZvx7OBmkgYen8Pr1ZS4uODgoHa670FLDgeJqz7Sz/y9C8zzrUPwdZ2PKN0WZQWeARwDzRl4LU1T1Mc+1xIDTMJ8hyjOfpfQZrUEA2M8sfG5E8yHiLCSO6TRazb8XsQgXlyGGyJIl5adQ/hgP0niPSaGD8eZ6MNesS1+id2rzwvcCA8zB5WPE+BDf5ytk+AQ3ToMOPgqDnATcjsNiEoAHa1jDczzHYH5wZAnOyntWmKkF90lrjeyWJnlpJzDA8Ui+DbycDv6C5JSpyBcEM4s++shmJm6Gg4MrXGgB2kAIgXCE+S3KhcVcJoc6oIzwE6ioh4AuXHxeSTOvxCPFrfwCxX+T5i7eMkXDhqLYAa06fhUv8uKoA7tA4OGRJ89gbhCtJu5XhRKl51o67s6DzEfxIA6nB4JnK60sYAEHOcie3J7ya45yeYHAO9lDn6yRKWmEj71AhuXA7+ng/ST5yoQNj4qiOJM7+/iz2ca2CYWjRqDRDG05olyU2nFgIQs5LA9PvDdUPpfTS9sWIAsCwNMgzhbMZjZ+yNd/qH+o9Nl6PVMlAGjWBpq4pccv5VDIwoh5mSf3taLEo4BfVycAdPJOfL5EgrnEMKrJJmihheM4jkMcoivfNTxo1fI+Vh6rwXVdWAZ6qUb2SdiIGeT6iKH5IB6v5Zu8h/fy+xquPHmkiGEyZy+mCYQjWMQi/pj7o3mpwtp8SxBC4M33UFco1H4Fj0PBY+ItwGI6uIHklLykx+CCi4vU4WyLUNAKFOzPo3bwBTnA0Q7OcQ4sBOEKhCvIp/PoLRoOYmZp/bTi8DYSvA2HvfyAD5Pmp7x3imYsCg46B8lkMuPvF6V1sbK1sTUAKWJo1uFyOnHAgwu4gA16A3uy0Uq/IgqCQFvhudupjBXYjIv/TgddJPlxhDOGQwMSDngHJr6HjUJTGv42s9lPAqN/5HiOp1N1hjuufI4yUrU3HVA8jcNWUpyqd2kyJ2TC5TjQ4AgHuUCasQauo4M4yaqs8NXTTRtwKZ551w5ykFwmRBM0yN6y/vYhknRGCwPsIU43X8Xj+7QylznAXFjYtJDjOR6lFLszu8mms2a2X26XrS/CDJReu4d7sQtXAseb7WRYRo676eArdMyAbIeavwbOIgE4MJ/5dGY6jUqtxvvnxBy8Ez3ENQIWFTdfATxIR8GePlkcwAGOQcBylo+tEaqGymdNg/Y1KqtwHRdvhYd7qQuXAKdgtEl5oJfjGeLHxLiT77Kifg2qEj3OEgEn5sCxxdWxBQDNZxFcSQzwTM33J3NP4mf8mp49J+7gnurCNRjNoOEHdHBt9Wcdn6JgON49bORypKCZGwjbKVLhnHXNcaVMT5+sM9HAOmJAFhynMASG+G6FK4ypyTAbM+pMNlfgECcGV3M1+Vw+9POpe8q+oFshSh6AHhajuQeX95EAZsHi+GKO4zi6ZTcHMwfxs7V1GtUiHIE3x8O53IGXYb4a89DeBPyMDiPNTksGmI3iU4FT5EpW0pftC514IxQC3DYXcZUoVQavAr5LR4nDReOZC7homMWsUKrsuhEIjG0e7tmueVbOB+ZgbGhprkLyDN/hM3TQMnkNaxAucHJxbXGhpG05gywFPoAHxOBMzmR3brcRPOuAEALvGM8IAQYPuIUOjq/LBSyNQRf6Sw0+frUmlelc8ncdcWAQ0kNphBOyCxQgji3b94YGtG0i1gZT2hd4IXQfqvIq8F8IiCAA9PIy4Ak8LqYJaIZLuIQu1UVXpguVU9NCAnbiDu4prnHPOKa4+Xrgf+hg7pQ1bDx8XgG0E4cVrGC73N6wgdFtdhGXCFhc3PQa4MaGXGx0ihqHIYbqK+REQAiB4zp4iwuakbMxCs80MVL8PfA4HYHb3cxEIKh44peM2EnxCRw84nAN17A5v7lug39JQ3CPc+HlxS2twD/X9yKWulN4DNS0HserRPEMDpvJAYfB8cLPg0WzMBNMw2vpCMolTRrXBlfsihAFrzOakpiF54L6DBN/8kGuxuVePBbSAsThVE5lfX49MhvehjtZCGG0AeKasoHuUuCX09IcEDh0CKPClPnGBjO7TS7iMmH8WA1fpINVDb3oMEYAcCKEFjUSYYQi9yUurIWSeelK4B46SowmMw1BqeodKs0AfSxE847gjVjP+pGOpvVqiiNwTnZK38d30MH5DbmYxTIRgRkgzrAZIKQSwPEcE7VjOA64sBFNHJVuliM4hZgxE2dkJrR/mO4fqf6HiQQAoyL8CS4ebdDitjCPeezM7kT702zkr8BtdRFXitJO/XLgn6auRaPQiwDWBhLdgZIYmkbitrml1qsm4EuTcuGC/X/aCAAFAl8ScaWA04ubT2emCwHjOQJKrkXgEoNruZah3FBDhXkn5phsBMN8aZLNTxZLKcYPYBDSqWhmgBJBFibXDLA2yKFzCqcYtX4ItNTwbNmmEAJAihYEv8BhHs0mjl8g6M/0T67ttgbcZhdxuTB2XsPH6eD6KWnM6M/XqQhOJma8bUNLdHVoi7vQLVUKv4IOzpiEKy+YjgJAgNvk4qxxSoWAFcC9dFS88jOFcgGgMhTwVYHg+QzPTMo77c52Teyy4WIqRQKLZbJQbMThOXJAVzQzgNPilGpQb5hEQbZo/x8rZHg0VFbB7uLqAeDRYGX0T51CAN8CziZu7IkKZWK2Z8bYX8RtdeEVZZu+Qwdtk90OERMjEy/HWBtkQlzEInR+8m6u8AS8tGzT+yfhskYAcDEJoabhs+TEnUoh4DQa7SypjUCklR51qfY+iVhZk4c1ACZJzzW45t3upXdSvgvhilL1KcCbGn9VS2RKUrWLI1VJY8wAN1djBhCegLOKq8uofKobQTdx4MpgDBlgIHwI9WDZjreVJoIbS+z5APBWPMCFkziJoWztKkKtTViWHJTIXonf4+N3FZYeH9kvkSmJyqq6zkjcuYXwL8MCzOebVERMUGJpfzW383kkNwZf6A52oLXp8FVGIfukuS+HCsthH7/PRw7J4fC5Gt9NZ5YD84ur76CD9trOOAFBGl8N+/r2mc/XXVh6Ckuvj+yTZumXyAFpnpeUNJ89o1C+aqgDoRN3cC5wSs1H10IDNEeFUCsGoeepHuTTcuTyjMR/0cdP+eW1M0IgPFH6hg8LAJLj0MxBwMVcTC6XAw0qp5D90nwfwXPXVd/nzmlzKBG/31BPJ6pirghLbbRC4Owdr3AkGY9RJznTm6rMAMIRcFLZptc3oG2VXIxDGzG4kivJ5cOlH9BKVyZkvrV0ZeTXNchy4As4QBwu4iIezz1e9YCstRnQ9JA2CRQeYsyiHYFKQye0ucFLTdiF0+SYm17tTEiUxcADfJQOvk6SnurOWEUbHIE4U6D3alMwI8H/AiABi1lMZ64T2SvRu3VluEYZGo1eoFGnKlgGXqtXdRyy4zmolyi4CzB59t4ADSxyIVlAFrgN9MDYDR5PtaUdDe0YO9yxmFYXQihFXCCaRCR13lg4CQd1iYKfFjd9iQ7uIFnHegpBDv0w+cRawD/VR5wnTCKsEN+38Aphn9sAWEoHgiQap2AOEPCY/5gRujsV/J4xU6NqNPpY89yJZQK3xa06P4E6W8EfAJPg+FLg3mhnKWuYEUhy0NnVSU3Z2R0QswROq2P6jJkiTKSYzyCvYi8UMq1XhyAQzpbQBLgRKgFSMsl5GoBX8XXezmoqnVHrRw5T7UPx31WlN1/FRjayiRxn0AXOCQ5ShjNNOq0OShSj324APhX5+tEw3v8C9rI3dLSOyirYUFxNAXeX/n+kAKD5GA4uTXAFV/CQ/1BVoUFaa1RaofdpImcBz2KKFWwF3aqRJ0pYAc58x3TuEZqjlTYZkH5WtnkOxinwlxFbVhNus4tcK9GbtElGMwuWsIQ96T34D/kmo2EYOjFZ/XaAv8LHPdWtrsMSIOaXpYy9mEYKAFs5puRhrA6F+eyjRMBoNHqeRi1XcLLRcNQiDLhtLvIiGeTOXgJ8FPjHqk9YyTNk2RFyyBqijU7QmzTiJSKUuUg4ApYTCACzMPqewzSzBAXEILczB78K2d5DQA/onRp/pY+7rIrnTlApjL+BWgQAH9Nf/JLaBr8C2tHIFRLOhVhbbGb4O0mW0MYPSFBbLQSBmZzlKA7anYTMAkhhkrNKoLdrI9jO4gfBoFV3NCbzohn2f0z1eQfWEedTgRlAinCRbSJWMGeZ/uwsOlhGku1VtiEM1wajddjUv4CZeA/zPyTL81WWCwADnAC8K9j6FE9Vla1N+xq5V8Ido/7bx8xxH8EkZ+3CzIkdTOqSYFkCnEMKl+eBHaCOVahVCvck19imJviitNTIfRJ+V7Y5Bfw5yckd/APcFhf9Uo0bd/Hw2LlnJ/x8xG5ZzN37H4z7xkFMQtWVwDnA2SjW0MV8NoLskYhzBW4s3MywFNEk0M06qHLVWKesLXwQM39PMFwnsvT3WH+XbpuDGUIWF5ZyV7fuwvI4qGMUallBU9LmlVe2C4FwBOIUgd6ggypvH6eDb5KsU7jG9XwY+HCofW9hMYvZyxxIeAkyfgiHUQHimBIBT3AvT/EoPquKs7KRg/96zBO5HdiB0a+swHhFrCTPlRxmDk+D7JY45zrGfhrhvjrNTml8+XnhjxyF54HNNZ2hHIURsJ8E/2If14n+Tk06Kcy3dAbwItULQgKTD6NgFlzNajbnNkf6/G6ri/8q3zxFbcC8QtvqTY5xNaURuJkYn2IA0oNpYvFYqAmvcIQxqg1PaG4AvlCXFlXSzSIEZ+NBE02kdTrUd6KVpqKnurVyn3IBQPNhXFP7+1qu5e7c3ZEffpVRqD+q0WaztwDfBR4MXUbRhGC9C3gPWZYGdZzlfok4R+AlvDFtwSqvUJtVZXWubcDrSFYERUwyQghkTpLbnTMVAIYZBD4BfGeMe/QMsA6ADuaS43sMcQP7QT+mkWtkaPVwsS2eMN37UwCcSgfHkCRc9Y+ovJfn6nq+DhxMd7UYY7E/HuPt8To0s+nEdOabwD/Zh7MgNitmnpmQ98hNuPgX+YGZpAnjCzD5tcBfiulIZ8ExHMMe9oQ6zGl2kBcXivO0sgrFKjTmk3y5bNd7MSVsHxqlilvRa5gOTiDL98hwNQdAbVA45zpm4Ax5T0VMGPF+JwCr6MCpukLlTj4NfK6qY0ejDVjERtpYqD2NEGLahzwXymbDEFxz8jU8zMPVZu/DLQwAWbJszlWXGMqb7aGv1IiYIJFI1N2RUKNJvVCnWm+r2MRGniXParrAOTG8GUC0lWlPGycAwDVBdd3zOI/1ufWhDtI5XVoRVAK/qdxnWAAY4Fjgr0oTg0RVf8mURP9GV5aBuBn4dFWDbpL9wL/Qwb8CV6L5OINcxW5jQ86fkyd2bKzcU1qDHJLoh0Y4P/wWeNtk2v3HRBcKM5QP/r8H3kkyZM+epJdv8ycI3o/D5xkgpp/XiLOidVjCKfhHPFXcdCHw69AnmEqSxflaJ4HlEb5dSHu7FuNlfj0DtLID6IX8GXm8ZV74AauQ/rPkRZ8aASBm2oIDXgRPK+EI3NNd5Cxp1LJLMDqU8sH/u8CNJEMokJPs4VuspZmbaOLf6QP/kG/ew5CDhXCE0fHtBIzL2RKoUn16I0ME5YbqxQ668UyFU1e5+P5U1hiOgIJtbCOTyVQtABSp8XDhClCEqvYZGU29v/F1xFlNLpoZwEk4xlxkNFAX08FxJDlY15YZ1gZ6zu1sD+0ArdJlMvWDJEcaTocNpIokDs3ETZGBwfxgpIdAZRT6t2WDfw4zoL2x5hl3EkWSu4BryPJhUvgcBtZDfmu++Cm00vjdPvpHIwb/fwJeMy0Gf4y3NbeXbXoA075wg3/Ae9C8my8zl/fRBgxAfjAfPqlFQLmTzulj7DVzSJIhya0keQsm6uMdDNFFP0Yb8IIf6R45TQ6cUFx9BR1B0etJRJT+Ge37Fa7AO9HDW+Xhxl34atm//w14T6jBP+Cv0LyZr9DO7SQwBjyP8LZegfFIGKbxYVRRiFF8JyK/S1OMLvyELRAz5lK/BjVmqS83EwP6jRkgUjTAcF4LAby27i3rwQWuCWT+fvpDZ/+rGO1GqP8hGDp7ESjeHgykT/FUJNWPlhq1QZVWgD4IXEaSH4Q+SRiMIPAFJBeTYztpYCv423y01sjdstKePgDcQJJPVq1irDcaU69+WIO1FTP4Vy/TtvKftBVcvfYU1PpRKJ9QNjYUcLJJkibJD/E4C5d70cBOyHflzSwlBMIRlKRJSgBXN6StjUQUHGK3yNIonGeAj4+i8g9HE/9CC5CCfG9EwbPcc2N6CQARwy0tM5xVPI/DBnwiJwUSsxtcHEhzLoL5gfo/54cL/1N5Var+h3EFAMlSNCfiwUIW0i9DShmmgcj9clgBa1xQXkeScIaKariJx4hzLh73oIEdIJ8d4XS4GVgzVc5+Y6GyqrKdHyRZY+3sS8jj8WkSQApymVy0zrj8eT+yBICA97GPZl5DM88igO2FQidhbpMAMbdsx/pL+pOASiujazJIjDNs9fXMz+MBmvkDHjCIcUINS7nQecIYe1ksk4WpDZCLWBsg4ZRW3byKjpK8s/XBhP85JiQzrFO+zhQduwGeHStCIej6r8ABPDiRE0PnGAaQGVmpzv4QyTr5Z47Hn9NHE3+KxzayjFbq8PpnFHMAACAASURBVAKSdXY6qwOqv+ze3onxTaidOD+ijW14QDqaFqBCWJieVRPrwRsYZDY30EaeHMhOGVoLUGEmWT3GXtMXDbqrTKr/L5I8UfN5m/g5c4BmSIhE+Gxq5c/crLH2s1gmiWEzwEBEM8CwdjAGXFfndhXT/+5jX7gjNOi+snd91Nk/lAoAhYvsZW94BxIN+lDZvr+j0sLYSG6gG3gt2aLHvAY+CbyeJP2T1o6QaKmNwn+YL1etfq3kLHxaeJg2wIWYG5vwkCLlE7cjUwMQcCkvMJtf0gxkwIuFc6gTMWFCmgzHNah1DUPlVWU+jm/U5cQx9tAExEyIUmj7ZLnWyQoAlqllFVtweAof6I5gBhAg2htkBuimHbgIz0RnDBEuG6+W2hj3hhlHAOhGAJcHg0CUHMMqN0Kd/Zm6DWhhuZ6NDPJWjMvDa0jyT9PG3l+ByirYVFztx2gA6ofLTuKAAwnCz8Yq9jtyNQABTXwvmLU20RTqPgmnrLLksTOtkp0e0qVZ/jZAnUx0gr0IwIdDPYfw9/sTLwd89OGybsIKAJbpQPVmgIXF1VfSQXOd2nMVDg4xk5TPz4bz01UZBXuLq/tgbE2fh2YJgpPwTM7/AzJ8jhOVKhtnHyLJg6EPrifv4Vd0sLRmW3qD0dkKtUwyQp7NcOwIkt3EiKABKKf2PLqTyXoc1kQU+NrYiASaoJVWI/ROgBBlGoAEMJvKgNfpiqayIMj36yaoS/Yigccxaa6rY3Zd2mLB1kOoiZuJ8c+BGSCWCJkUyBXGKGiGzlZM+bnbxjsmJEX1/1a2hg7/q3jXfzXehNhDcTkO4MCxHMv+/P5wTdOUev0DfC3cgQ1img/+QGWe9cfrfn5RCH7U0WLFpyXPEgdWoTkHxUuQnIBiNiaKfTYwG80cFM08RBpNH+aJ7Css5m9NH4pe8mxB8whz2EM7+5Eo2nDmM58DYRL7CSr9AI5jhggAWmmT02+Y39ft5PvZx26IkJ10NKwGoB74sLNvZ6WvRzQ8Uw/BbS6ohKOeShvNsBpUY9aVqBmNyfZZb1bxAhv5Iz7n0h0hKdDIlOo3UKsA0INAD6f/7aEnvPq/3N1vTPU/gIfk5CDByCEOhbb/K6lKk8dA9Iz/Rx/lGpyQHh0REOwKSnnOOAFgO01IXoPPq1C8BM0ZCGLFXOJB4p7Cs1qMOx8CBmhG0gwsLKrtStV3wbEaSLOPFOtpw0FApjw19viU+0ocB2yp4pNOOiqnTMpcQy/UMRPmJeTo4HhqC5wLl3rNMjo+5g4+XJMWpohu0/grfJyXRE/zLAcl+jZdGuY801hHgnOjJgUSzQLdroPY++vpwIuUW6MSzUoEJ+DBUpayV+6d+BgKZubhLLwDTDAue+jhgrBRKj/prC4tyLKNZAMGtCON8sch3DcaBbcwms0UAWA3gjwXoXkH8CY85hLH+NI6mIHbK1lczCzcM8lw4sSRSPwB38w2go7QZzg9qo/5Xwoz9A2wGMkNxMw59rAnfAdXPsS1jbHX9KP8tX6QZJ0HXPvuTy27MLO+3ol2DMkgsB9Ui8Jd7YbOLKryCn3PjB78AW7G47P0Q7o/TawpnBnA8RzUmQruB0x68kuopchVEP7nwjzmsScfLkdcRfGf301kZvaA+UHH5kcQWHSu7EJTY/ufSWgq5zn1FwBKZr/1zr9dVzoR5HgbPp/E5RTiFAd2mszfLi6LWUyGDD4+efIolMlzpjVamUUogRfzEHFhyrk6AiGE+V1yD3zpk+/KG6X9QiMgLWABh7ONKXswnaioHPjYVLXD0iB28Qy9JS6qtXIcS1jIH2iFeCxOVmbDqZ99TUki3Keof0jcWNRPoF3FdjbyOD7n0Q3uyS6+DDEuCipjg26gNgGgaP/fxa5Qmnmtyu4/TKD+hwoBQEa5j+X3ZMcYe1lKKb+99akoNxrTeOznIKuQfI04l9GMeQJbAQeWsYwMGfrpx1c+B/IHyh1fxnkHtNagGP1FKThGerM8mG3yuwtHcDhzOLRjzYxmsp47y9TwVnLU06S4lTgxYDbMZ374+PPy52zfDNYMrSPBeeQLzr8hq4g6LQ6qRQV1Cm6gg7+tytm2mxbg8kDTmSIVTgAbWfxnwhwzDjAv8Pv28cOrQ8v9ChvhknFEoalQjdWSgW0m0ksLnXwWh6dIcBktQDvEZ8U5zTmNWcxiT34PhzKHyKQz+Fl/uMhTrfm/dfnfMivx0/7RMfhDpbB+5Ks8LLXRjDHDuTVFE81kbsYD+iDdFyEpUEyUJrU+EVPDsxouwyGBZ+ry5HPh6jtXFP+5L0ztG1NOteDcFEkDYAWASOicPnqVr/3MBu4kxsdoxmMu0AKrWIWjHXZkdpBOp02ay6NkTJ5Uyt/VzilqhWWmMFNMiY1iFTtxeBQJ9IRPcS2EqExqXW1SoGsD5+dtbAtXlXdkZMSE6n8IBABMQhSlIoRTl7epXu4nRywVjjRHT8TEAHMR3IHLxTQDrXApl9JKK1uyW/AzR9FMfCoY6XtiBQCLZWLWkYAyM0AInFantM5F9QJA4RxdIyv4jorKq0pPvJACgKSVjCmMEqUCYEW6mHplPjpyKb+1M9tPNiyDzAPuRLCGBBA3s/71+fVkM9lwkq2ldsrlemsCsFgm5paqzQDDlUJW0sGKSFftZgmCFXgwl7mkVTqc/T9dlunzaZK8GOZyDooeUpAjF815rFwAWBDhyKOTo22sSyEQ/AyH80gAMePktyW3xar6JxtrrrNYorGKF3F4JLIZwBGwtGxTVC3A2iDPyemcbnJ4TIQG3R+u+E8lDprDQWfsuBGywJbfDysAWMrRvA24ghjgmcF/d253NC2TpT6UuptO0zoZFss0pCozgGirqThQMfxvJzvDhf9JDU+XbfpV2Is5wGEEIMER4QWAinKzx4Q+0HLkM0g78HkcIAbnc35Ng79WGpVVyJRE9kv8Xh+/28c/7OMfKlk6C0tXYenxkf0SmZZHrdZBSw0vTHUrLJYZybAZoDe8GcCJO6Ulgi8oZMqcmG5iwCsCASBsYT6VUTCcwX8P8MdQ18O4KxiboALXdUPHPJIoWzs97AUtRwGaz+ByLE2mwNQGtSHy4K+1Rme1CW05CDxQbVPMdfU8jTpJwWIQ8wRuS5W5zmcY2telefptvg6LJSyr2M1GHkJyMb3gzgmXFEg4Ak6htPLr64CvhrjihQhm4cHlXM5D+YcmPoJRi/+E7tWMBgBAmuxroQ+MObCkuHoZHbSEPthy5NLPQjQ3Bo9SmjQyFy1Rl8op5IsS9UMFt1D14F9GNyY32W9B36rxn/BN1MsRHuVUIXhtH2s/i8UyKtWZAWaX7fj6kNcy3v8OHORgqEnTKBq+0PZ/KNUARBQAhCPgNCjUn0sAlwG/i3JxyxGI5HpcBHG4lmu5O3d3+Fm2BpmS6Lv0aMFqPiZ8cjvmv8FyGJPgN6gUULrEgGOBFRgt1QrgRAaoKtf5EcBR80EtlrqguQWPLwVmgFhLyNoAcQe5TAYi9+V0MJ/khDF9Rfv/oZDlNVVWwXPF1X4iph/2UIWuNg9OlFLwAkR7WQnEV2IFAAu8LniIn+bpSKF+clCif1K2v8IM+j8BfhHiBZqYDlo5nrNYxEPMipbrfEZypH4ui2UyWM1eNvIgkkvoiWAGcAWsJBAAXODVwH+OeUA3xyJ4KR7EiTOkh8KF/6XKdro9aoZZB8l2fCAVsTQqpgQi7cXVt9HB3EgnsBxZ9DAbuCrIYd1HX+gBSOUU+vaynQ8B55LkFST5Tl0Gf4AkKc6nnyXAIlhgA1gsFsv4FM0ACMKbAeZEiga4GgF4sIY1ocymWunKyh6R1P9gBID7kCgOw8DgQGhPRzAlEDm/uDoP+HjUBliOICTXoInjwlVcFTqHNRrUAWUU+YY9wKUkK4Jb6kULp9AMtBhp22KxWMbhZ3ho+iHTkwkfDdDkUOL/v5YOWsfZvZj9bzvbQ2VH1TkNjxRXfeD2UA0rbSMr6AUexQf6wyc8AECAs9ChZN6fpIMTozbCcoSgOA0NOLCDHaHV/yqv4L7iqgauIMmWxjQS8LiSBCBgqFC6y2KxWEZlFftweBAJ9EZMCrSquNoErB11xx4cSuz/YTWnFcV/7iUZPSV/YPS/izigICZikTyjnbgDlxZXm4Bv03F0lpA66lHDFbFTEbId66ymxPr0NEm21bdhJfQzC5c/j/qyWSyWo5p1NBHNDCBAzA9hBtCcg2ABMTibs8n5Icz4GiqMopHV/zAsANxJDEgbP4BIFaAEuAtcOLW4ZS3wH3RE8Si0HCEsDB6dPCHV/xQEgGHurWeDRiB4Jy6ziMMVXEE2n23o5SwWyxGA5me4aPqimQFEkyiU2wPg1WNMjk31P8fkLVH+xMk6Ryn+Ezr7XylmkNY8giBFP+S78saDMQLCFYg1ojQf4FuBL9JxpEdZWyo4LvjGfSb2lC1S/rw/XMf2lJOiHYcPB9Gum9mMkjYzrsVimYDV7MfhfhSRzACO68CZxdW5wBWj7FZU/+9jX6jz6rSmZI71JEl2hTqwsn0ArCSH5F4kcKhQ0Sji0O02u4irBcwpbkoiuI2vsbiahllmIIqFwWDu44dXrZe/Swvr26gCKRzgvxGcTAIWs5hu1W3V/xaLJSzVmQGOHScpUDezgYvxTBh+itTEfZIG3Vdd8Z9KhtX0OX5QaBDZrmykaIAAt82FaxnWBGiuQ7CR7/E2fmi1AUc8EhXY8sMUsQgQibJH46V1bVOA5lPAKwNJO0cucoZCi8VyFKP5OS6KPsh0RzADNAuYVVx9bYV5/EocPGImcsrPTaw5VVJVZvuvgwCQ4RbgKfLALsysrIoh25vtIa4tJEFIADnmkuOHeKznZ7ybn40bCmGZySg6yQMRx1URFzC7uHoxHcEwXQd6cOnlH9B8IihOtIIV9GX7Jjx0BNPBq8WK0Y3HhaqewOnwfFgax2oO4HAfGugDNx4yGiAmSs0Ai4A1Jf8uhv9tZWuoyCmdKcuU+iKVtQAjMPyYX4RiPf9AjF/TD/nDeWLt4dIeVuI2u+iLNHK5hEeBXmCI84HzifNFbuOHxPkdDo9xNQerbXwZT+Ngbu5yYDma5aRZzqPMIsmr63INy0SYx1JGKyzlxBzUS4qhgKcAnwM+VHNrDnMCkv/C43JcTIzKL2FzejOsArFE4CW88NkKpzK2JYYRrJzoGTuPKPpx6MGlk8iC5oQkMPVNYsRImF9h4rEBc5/nlKx3EONszDNX7XegMLkxtqG4qe6f1lId60jw8qL9PUQfJ4SgwhB+A/AwAwj8Yft/NyFMkhr0QPXFfyopl3N9fkuMh8hzMXtAz9fmIazi9MIVeAs95FqJ3qVNapcU0M9smriJ2dxEHLiLXQgexWMTLj0I+oBeHPpwyOLQhqANo0RpKyzmb8EsHI7BYTmwDGhCYCRxB+gBQNGBRzKKV5qlKjSdKCAXXjoGTD6Jkx3UAhWIEB+kg8208i3+soqnbyez8Hgz8C94zCOB6Yi/XLLPY6APaPIX5onFw3X0oqUs9fUbuYXZtLMFly1cESHusRpaMJ1NCyQqSnGOh4gJdKumwa2bPFyuYhZ30E39BYBCRlRmA64JydogN4Q+XLQJdFMhpHU2OTxqE8AUpoiVz1eA99dwJku9MGaAr9KHk+nOEGsLWRug1UG1quA9vIH/5GNoTiPOEuKmauoBeWCCsxSy/20t21S1+h8qBYCXoVnP3+NxL73gv+gTOylWfbEUAW6TCRHUyzVyQJrKRTlMqZZBIM1JJDiJVoadwUTJ79Kl8n8uFDt3l+EOwcMILXcCFDUDu6v7EJbQ+OxGASnTGUbBSTiolyv4NRRy83yDBO/mFv4fzfyc6ybo7jfhEOflCN4J/AkeLcSBVswT8FzZ3uvwuYoM89kJ4gyBzod4iZsc5OkSngcSvIsm3lV8Hu9lL5otwBYkmznIFg7zGMmKqh4p5uKziR1AmAhEgXm+0ywK6m12jlIpaczDvUIykkcBeAU/YR8nEk3FnQWeBGAtSZ6JcGT9GcC0fSWmEFk9hQCX4jRjDnNMlEiIkKzi4QkX/1W+CWRtL5yrWmOWwnT04b9qy2SwmkNs5B40V9EH7jwXPx2iNkBMmKy59wJwCnN4hBwX0AwII9SrfIjwv6yCzcXVXuD+6j6IYeTjuYb7eIA7yHMN2yHfVr0poIgoaATmeuiXaoQQaFcju6TJ+C4xAkEwcAe4hcUx5yjO7J3C9hhGHxA3lQwXsIA8eRSKnn09pS04ASsANB7J3Sg+Qx/0HdeHK9zwKlSM/4h/nW/K//QAac7HZR3N7OceNuOyG8EeYhwkTjuC44HFCBYDJ+Eyj2aMUNgCxGElK9mhd5C5s5hpqJMkb+J2rmAe9zAL4l6cjJ+ZWJXnCsQFAp3QkMYE9TiYQSnN8bgcj8vLyRHU2Pww8IWykwwi8FjEEoyvzUTvvIOZkbYBTcZR6IH8A6G1csIROCsdVErBDqCVRSQIPzApSsuN1s83o1okRa3kmhVreJZn63ZqB4e5zEWh6NJd+NkIkSwAArz5Hup6hYgJ4s3xaOaaAhpNejCN/r0NUZmmrCPBVeQKzs4hzQDOEgd1hjITiGO5gBagufBO++He6YriP78lGSHhyiiM/kKneC+SR8hyHBsgf36eWHMsUmW3sRDCTJmENAIBc80sxY25ZS+LKPyUbiv1LC/9W6GQUtIlu0wbNZWzqxNqbrhlYhSPA90MME91Krz2CPZ1MB3oXA/5Soneqc0AmQEyLMJhUVFILBTNwGO4CHAT0EzRDvtSXsoudrHN30a+u+wdMcPZqXThArNhLnM5wMTqNzCzPH2eLmq3NNqcvwuj2cowfiYDHzOItYM4U0wsWDvgCY8VrCBFigfyD0SalYLJ1inWCORKae7VHMI7rHUBD0S6XOPRgDSakFwmFyniZCLSpIevUSVOzNzcfKbKvlmD7reD/7RF8wtcvkY/brY7G94MEHNgDaiTFWKZoN1t51iO5QH/gVCzf600FWkCalL/w1gCwLXs5FauQ3M/g7TwJPjn+7ie25C4ae1rfN+vzl42VnvKTdC2PsFksBLJJu5C8Ua6QSwQZsCL+My4TS6sAHWKQg0oU+U6OIfAzPCbGR70E0ZgPJVTSZOmhx425DeYl1KNiJk1FjSPruB5izpLC8J/VFYZoaXVgzazPd8ZotPXQA7ObDqT5/Xz498fZT7zFr3FfJ5qrXGuwGv3jDYOgVATv2waTb63pglGQ9GFn2mby2G6tstSG6vpZCO/R3N1FDMAmAJ6zgkOQgoGGWQgPxBaS6pzOjDlgclG8Ltqml/K2Cq91/IEv+SNaH5FL47+o0aeKxsmBAD1PW/5J7MagMkix80I3shhyB6TJTarSvORKLws7Y6ZLXsCx3VwHAcHBxe3OAAoFEopdsgdIwZJlVfwh7Iz/6zwuxuAvKkHIBDVzST18G8tNVFcTdOkzWx+MgcKbQTu0J/VJkq0WEZjHQmujmIGKEVLHblfVENlL+PvSdIf6QSjMP7U53X8Bof3oYBDoNdr/CG/qiRBk42IlbXRCgCTxRA/R/EkOWAnaKHrEoqmfY3MSvLpPNlMlqHMEOlMmkw6Qy6dw8/6xlm14p0qahAM24DfALCEDDBEFgYYsDHcFoslPMYMIOmvPnFexOvVpfhPJRN3e6/jm+T5LEPAfuB+yO/Kh0+FOFWUawCsCWCyuBiF5uO4QDf42/3ItSUmRJcs46DyCh4r2/TvJMv8xrtQxoek7m20WCxHLqvpwuGuqEmBqkXlVKW/f1XFfyoJN+/x+XvS/BWDDNENPAH+kz7Sl9NWCHA8p7Q4kdUATCYp7kRxJ1lgO+T3RS8wVStaatQLqjT2YwD4j4rdin4ArtvYF9hisRxxrCMB5M0kopFjoU7rUnPc4yTZW4/zhhMA3o7mRr6N5FyGeJIBYDvo+zT+Ad94J043QcChdN6/qK7pZS3jcxWaQd6K4gUywEbIH8gbNdkkPCdaaeQuWVku8+9GsZkZpZqM7ghosViOcjS/xMWnD3JducZNcjTozvoU/6kkWq/3N2zG4yLSfJ5+YC9wN8g7Jf4O36hcp4kgIIQwyTgMLo2qMmcZndfQiWQtkkP0A49DfnPeeLw26hnRRlUmt0m4q+w/XyTJV0c5ohsBKJNHYro8uxaLZQawmm6cQrq5vvAlgqOisipIIBTw3/U6d/Rpz1+TJclHyHM1GXaTwsRr3wfqtwp/g48/6NeWOKhexMvWjiozQD1jo6vm9WwnxytJ0UkP8CzIP0j8Pr/ugoCWGr/bR/1qxMvyM+AjYxxW1AC4TLIJQE+T72g8pn3zpnkDjwAi3eOj8+tYVyh6h6QxJnE1WOb9fx9JdtTr3NWrxZPcRQfLMYUNPkCOl3EIE1z1JMgF0hTWONaUexVxYRIhNEpLorQJbyqEYml/RP7z+goACvaxz6Q3DpPBaWgS346obcs2sG1v5Y98gzPJ8l3yXMcQsAfkYgmngZgrTKIaEfHB0OY7V1ll7u12RquJ9Svg7STHDGYzAoAqmABKb0MwQJc4HGpK7HDBtiCGN9gvQzg07GXvxN9RwdnWSTgmrW+VaKVRGRUtdW6UzzPZKNjP/tDPeCjEcGlqndPRQiALqclFrJBWutbKI0M1Hl8PpnMfN13Q3IpLnn5i+T15RJMof24Kz4WTcIyJIOIt0lKXZuIE+H6tTS6lNru4SUO4DlhHBy8F3o/Pm/GJswdTAIhCwo7ZGnWSMnb5ZorpfYUjylL8FgeCoHOt9PhWlHe+PiYlQhewCcaJjKxPJIAqLNsg88A06x2De7MT0velp7gxJdzIQb7Ga8jxXnJ8AWihH3gBdLNGniDhJGCWSVgTpH8WQhTja7UqdMiy8FJkMCk1t416xV3AB5i4UpYRAHLQleoafnYUw4VhUpjOOI25ZtbsT57qYuSD72gXpH8Q/juSCwsCdftEe45xzcPAE1UcO90I3r/NkH0oTDGFaOj2gmaqu8rjF+iZn78/eEZ3QPr+adSPTEdW08MG7iDNddwBenD07kaulHAGxBZEy4uiMoqS6hsp4JZam1xK/RzjkjwBvIsOPgq8B1iLKX/QBJjO9dnCUkKliknHC+u5urUsYPHEu4RgANOZPlmXs9WXQUwO/ccm2nEKeB8a+AYdrAP+ArgJnyUMYAr1FIr1lKV7drSp9xC+n88B/wb8E8kQ9e8k3eQwtQf6Ql+jNgYL11of8bgDheVopx9TPyR8kb5o9Ey8y7jM9MEfpnc/Mh0ZYB17uI7BcfbZDuQgvyZvkqOFSZGuQfeU7XcLyXGvEpn6e8ab6mefAT5DB3HgbOCikuXkcY+vbeAfwihMtpb8Dpb6dJ876OSFssKy1bCxLm2p5EUG2V5z216sS1vGIkk38Hk6+DfgVcCfAhcCp43YVxFm8D8A/BZTR/AukgyEbstuunie8dTcstCCYN4/2t/j/W+kmLibbJXf0RzgnVUcV4oPfK2G46d+eNvGbnbU/IxPxAcw3101VRBOBV4JdW3j5FZjeJEBttXc/k11actM4AC/omuC+7WAM5jHK4iZmgAyO7EtTskReUz+s6Z2jsLk+z13sAg4E5iHUWhW/m7HdHY5jOI1UMBWLsH2boYH+30TqHwt05UO5gNrgHMxGRzmlSzNmDnJwZIlmAc+MY6Nf3x+SBvdnMxYA3my7hXnLRbL0chOfsQ83swsaKONTHpi87Hsk+h1xeHsRWBZ1X3dGNjAJ4vFYrFYGkUfp+OwiSaE8ATNNJNLT6Dq1uBvK5RGN3yaJP+n3k2z2U8sFovFYmkUDp/AReDBFVxBPjdxhU2VVaWDP8APGtE0mx3PYrFYLJZGMMilCP4sGGkf5/GJHQA1qK4yTf8DJMeId6oRqwGwWCwWi6XepFiI4KcIHGJwGZeRyWUmLmKWU3B72aZvNaqJVgCwWCwWi6WepPCAnyBYRBO0086j+tGJcwBoUAdUqZDwDPDjRjXTCgAWi8VisdQTzWeAy4lhEt4h8LMTp4eUGQl3lG36WCOjkawAYLFYLBZLvejnL4GP4gIerGQlA9mBCVX/Wmn0nrKd7gF+17B2YgUAi8VisVhqp4cE3Xwd+DYOkDBe/y/kXwiV+U8NjShk9tFG57WxUQAWi8VisdRCFyeiuQWXC/CAZriKq3jAf8AUppsArTT6hbL9fkqSxxvV3AArAFgsFovFUg27SeDwZuBzeCwgAcThEi7hAf8BVD5c4j7VX5b2Nw/874a0twIrAFgsFovFEoWdLAT+Gs2NxDiWGNAKOLCc5azPrQ9d9U/lFPrBsn2/3qi4/0qsAGCxWCwWy3hsxAVW4nAeMa5G8AYSxIhjKpU0wTKW0UUXu7K7wlX7w5Q2V5sU7C9u6gf+byM+wmhYAcBisVgsFoAnaQNOQHACghNxWI3D+cC5CFqLM/1moAWIwUmchESyR+4xKv+QbntaaeQOWar618DbSU5e1U0rAFgsFovl6KCfE+lmFf2cgOIENCdA2TIHgSmT5wEJoInhkTKOqVcLXMAFbGUrB/wDKD/8wA+gtUYelJX5/j9Gkl/V8vGiYgUAi8VisRwdOHyQuXyQLOBjAuFdIIYZDRUgC9viwCyg1STyOZETyRd+0qR5MvekUfVHDdTTIHsl/Lps638An6/tw0Xn/7d3NztNRGEYx/+w4AK4CO+Be9DEeAVuXehcgLfhyrhzZ0gkce9CjVswSk1MTAsaS6pUWxwobc/7ujhAO6ET29oB5Dy/pJnJdD7O7n3OOfOhACAiImnokrMC3ADaxEJ/MpS/zDKrrGIYjrPEEgBDhgwY0Ow3R3P78z6d7xDyAOuFra+Be1U/8z+JAoCIiKQhkNMD+rC2ukaNGoGAYQQLdIadyTfwLaI0O4SDgG8UTlYH7pBxvIArzEwBQEREUpEDMITGSoO8ly+muP+FmxP2A2wUNneBm2T8qL4FkykAiIhItSnffwAAAVtJREFUKnIccM6G+KtmfcO+GLwsbN4HbpNRu5BGlFAAEBGRVMQRAI9z/lXy4IRugFdAq/DXNnCLjHqlDZiCAoCIiKQiP12pJAA42MCw3OAzsHluj+fAXTK6i7/47BQAREQkFWcjAIucAnBzrGd42+ENcHBulz3gAbB+GXf7l1EAEBGRVCwmAPjJF/wGHj/ju8v4G/3GHQNPgIdkdOa/YDUUAEREJBWzBwAHC4b3HQbEz/seAQ3gY+lRNeAx8JSM9j+2uTIKACIikorD0xXHRz354BAYLS0uGQK/gfcwxRv6e8AzYuF/e5WG+ssoAIiISCriCECAncMd/KvHO/TrwM+5zvcdeAe8IPb25zvLJVEAEBGRNBg5BmyBf5ipg27AJ2Kx3xpb7v0PPf0yCgAiIpKGDke0iE/iF/0CvpX8doFtstH0wXVxMa9CEhERuQoecZ9Ai1GBb17H4j6NP2c10DUQmXdCAAAAAElFTkSuQmCC"/>
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
- })();