25
26// Converted from: https://github.com/victorqribeiro/imgToAscii/blob/ca7e181b9bb9770798ed3a0d3dfeb344c60953f2/src/imgToAscii.js
27import { createCanvas, loadImage } from "https://deno.land/x/canvas@v1.4.1/mod.ts";
28export async function imageToAscii(src: string, maxWidth?: number) {
29 let string = "";
30 let stringColor = "";
38 "`","'","."," "]
39 }
40 const image = await loadImage(src);
41 let width = image.width();
42 const scale = maxWidth ? maxWidth / width : 1;
43 const height = Math.floor(image.height() * scale * 0.6);
44 width = Math.floor(width * scale);
45 const canvas = createCanvas(width, height);
46 const context = canvas.getContext("2d");
47 context.drawImage(image, 0, 0, canvas.width, canvas.height);
48 const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
49 let grayStep = Math.ceil(255 / alphabet[charType].length);
50 for (let i = 0; i < imageData.data.length; i += 4) {
51 for (let j = 0; j < alphabet[charType].length; j++) {
52 let r = imageData.data[i];
53 let g = imageData.data[i + 1];
54 let b = imageData.data[i + 2];
55 if ((r * 0.2126) + (g * 0.7152) + (b * 0.0722) < (j + 1) * grayStep) {
56 const char = alphabet[charType][j];
1# ASCII NYC Traffic Cameras
2
3All of NYC's traffic cameras available as streaming ASCII images: https://maxm-asciinyccameras.web.val.run/
4
5<p align=center>
6<a href="https://maxm-asciinyccameras.web.val.run/">
7<!-- <img width=700 src="https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/9c4e7e22-ae90-420e-4238-df0013d2a300/public"> -->
8<img width=700 src="https://i.imgur.com/83LBOcN.gif">
9</a>
12
13
14NYC has a bunch of traffic cameras and makes them available through static images [like this one](https://webcams.nyctmc.org/api/cameras/858ef4e8-5058-4db2-96e2-70f71b65aa24/image). If you refresh the page you'll see the image update every 2 seconds or so. I thought it might be fun to make these cameras viewable as an ASCII art video feed. I made a [small library](https://www.val.town/v/maxm/imageToAscii) that takes most of its logic from [this repo](https://github.com/victorqribeiro/imgToAscii). You can see a basic example of how to convert [any image to ASCII here](https://www.val.town/v/maxm/asciiImageExample).
15
16I pull in [NYC GeoJSON from here](https://observablehq.com/@miaozhang/maps) and then hook up a Server-Sent Events endpoint to stream the ASCII updates to the browser. (Polling would work just as well, I've just been on a bit of a SSE kick lately.)
106 Your description should include:
107 - What content is on the page
108 - Other elements like sidebars, links, images that may be in the page
109 - The author and origin of the page
110 - The stylistic aesthetic of the page
143 - A footer with author information.
144 - Some reasonable links in the body text.
145 - An image. You can create an image by using the following markup:
146 <img src="https://maxm-imggenurl.web.val.run/{your image alt text}" height={image height} />
147 - Specific, concrete information only. NEVER use placeholders or other generic text.
148 `,
1import { createCanvas, loadImage } from "https://deno.land/x/canvas@v1.4.1/mod.ts";
2import GifEncoder from "https://esm.sh/gif-encoder";
3import { DateTime } from "https://esm.sh/luxon";
11 const height = 300;
12 const canvas = createCanvas(width, height);
13 const img = await loadImage("https://static.esm.town/build/_assets/valtown-logotype-blackOnWhite-R2XK5XHC.png");
14 const encoder = new GifEncoder(width, height);
15 encoder.setRepeat(0);
32 ctx.rotate(angle);
33 ctx.scale(scale, scale);
34 ctx.drawImage(img, -img.width() / 2, -img.height() / 2, img.width(), img.height());
35 ctx.restore();
36 angle += 0.02;
38 const now = DateTime.now().setZone("America/New_York").toFormat("yyyy-LL-dd hh:mm:ss");
39 ctx.fillText(now, 35, 50);
40 encoder.addFrame(ctx.getImageData(0, 0, width, height).data);
41 }, 20);
42 },
50 return new Response(body, {
51 headers: {
52 "Content-Type": "image/gif",
53 },
54 });
9 },
10 info: {
11 imageUrl: "https://jordanscales.com/me.png",
12 allImagesUrl: "https://instagram.com/jdanscales",
13 status:
14 `<span style="color:purple"><em>~*~ do you realize<br>I could have been the one to change your life? - xcx ~*~</em></span>`,
103 name: "Tess",
104 url: "https://twitter.com/_tessr",
105 imageUrl: "https://jordanscales.com/tess.png",
106 },
107 {
108 name: "Gianni",
109 url: "https://twitter.com/giawni",
110 imageUrl: "https://jordanscales.com/gianni.png",
111 },
112 {
113 name: "Jenn",
114 url: "https://livelaugh.blog/",
115 imageUrl: "https://jordanscales.com/jenn.png",
116 },
117 {
118 name: "Sunil",
119 url: "https://twitter.com/threepointone",
120 imageUrl: "https://jordanscales.com/sunil.png",
121 },
122 {
123 name: "Justin",
124 url: "https://twitter.com/modernserf",
125 imageUrl: "https://jordanscales.com/justin.png",
126 },
127 {
128 name: "Slim",
129 url: "https://twitter.com/sliminality",
130 imageUrl: "https://jordanscales.com/slim.png",
131 },
132 {
133 name: "Lex",
134 url: "https://twitter.com/lexbyanyname",
135 imageUrl: "https://jordanscales.com/lex.jpeg",
136 },
137 {
138 name: "Bill",
139 url: "https://twitter.com/bill_pond",
140 imageUrl: "https://jordanscales.com/bill.png",
141 },
142 ],
151 name: "Steve Krouse",
152 url: "https://stevekrouse.com",
153 imageUrl: "https://pbs.twimg.com/profile_images/920962798590595074/MHJaRnhd_400x400.jpg",
154 },
155 },
160 name: "ⓨⓔⓕⓘⓜ",
161 url: "https://yef.im/",
162 imageUrl: "https://jordanscales.com/yefim.png",
163 },
164 },