import { Resvg } from "@resvg/resvg-js"; import { promises } from "fs"; import { JSDOM } from "jsdom"; import * as d3 from "d3"; import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; import random from "random"; const xmlns = "http://www.w3.org/2000/xmlns/"; const xlinkns = "http://www.w3.org/1999/xlink"; const svgns = "http://www.w3.org/2000/svg"; import {RADIUS_OPTS, DotMaker} from './src/components/dots.js'; import {PALETTES} from './src/components/palettes.js'; import { GetColorName } from "hex-color-to-color-name"; const CELL = 10; const MAG = 2; const WIDTH = 20; const HEIGHT = WIDTH; function randomise_params() { const palette_name = random.choice(Array.from(PALETTES.keys())); const palette_fn = PALETTES.get(palette_name); const palette = palette_fn(); const patterns = [1,2].map((n) => { return { i: n, colour: palette[n], m: random.choice([1, 2, 3, 4, 5]), n: random.choice([1, 2, 3, 4, 5]), f: random.choice(RADIUS_OPTS), r: random.float(0, 0.4), }}); return { background: palette[0], palette: palette_name, patterns: patterns } } function colour_to_text(d3color) { const rawname = GetColorName(d3color.formatHex()).toLowerCase(); // some return values are things like "cyan / aqua": take the first const parts = rawname.split(/\//); return parts[0].trim(); } function image_description(params) { const bgc = colour_to_text(params.background); const dotc = params.patterns.map((p) => colour_to_text(p.colour)); const a = bgc.match(/^[aeiou]/) ? 'an' : 'a'; return `A pattern of ${dotc[0]} and ${dotc[1]} dots on ${a} ${bgc} background`; } function poptimal_svg(params) { const window = new JSDOM().window; const document = window.document; const container = d3.select(document.body).append("div"); const dm = new DotMaker(WIDTH); const svg = container.append("svg") .attr("width", WIDTH * CELL * MAG) .attr("height", HEIGHT * CELL * MAG) .attr("viewBox", [ 0, 0, WIDTH, HEIGHT ]); const background = svg.append("rect") .attr("x", 0) .attr("y", 0) .attr("width", WIDTH) .attr("height", WIDTH) .attr("fill", params.background); params.patterns.map((p) => { const dots = dm.dots(1 / p.m, p.n); const dots_g = svg.append("g") .attr("id", `dots${p.i}`); dots_g.selectAll("circle") .data(dots) .join("circle") .attr("r", (d) => dm.radius(d, p.f, p.r)) .attr("fill", p.colour) .attr("cx", (d) => d.x) .attr("cy", (d) => d.y); }); const node = svg.node(); node.setAttributeNS(xmlns, "xmlns", svgns); node.setAttributeNS(xmlns, "xmlns:xlink", xlinkns); const serializer = new window.XMLSerializer; return serializer.serializeToString(node); } async function post_image(image, alt_text, cf) { const status_url = `${cf.base_url}/api/v1/statuses`; const media_url = `${cf.base_url}/api/v1/media`; const headers = { 'Authorization': `Bearer ${cf.access_token}`, }; const rawData = await promises.readFile(image); const blob = new Blob([rawData]); const fd = new FormData(); fd.set('description', alt_text); fd.set('file', blob, image, 'text/png'); const resp = await fetch(media_url, { method: 'POST', headers: headers, body: fd }); const bodyjson = await resp.text(); const response = JSON.parse(bodyjson); const media_id = response["id"]; headers['Accept'] = 'application/json'; headers['Content-Type'] = 'application/json'; const resp2 = await fetch(status_url, { method: 'POST', headers: headers, body: JSON.stringify({ media_ids: [ media_id ] }) }); const bodyjson2 = await resp2.text(); console.log(bodyjson2); } async function main() { const argv = yargs(hideBin(process.argv)) .usage("Usage: -s SIZE -o output.png -c config.json") .default('s', 1200) .default('g', 'config.json').argv; const cfjson = await promises.readFile(argv.g); const cf = JSON.parse(cfjson); const fn = argv.o || String(Date.now()) + '.png'; const imgfile = cf['working_dir'] + '/' + fn; const params = randomise_params(); const alt_text = image_description(params); const svg = poptimal_svg(params); const opts = { background: 'rgba(255, 255, 255, 1.0)', fitTo: { mode: 'width', value: argv.s, }, }; const resvg = new Resvg(svg, opts); const pngData = resvg.render(); const pngBuffer = pngData.asPng(); await promises.writeFile(imgfile, pngBuffer); console.log(imgfile); console.log(alt_text); if( cf['base_url'] ) { await post_image(imgfile, alt_text, cf); } } main();