{"id":13624249,"url":"https://github.com/joshmarinacci/node-pureimage","last_synced_at":"2025-05-14T02:06:45.117Z","repository":{"id":23193778,"uuid":"26550221","full_name":"joshmarinacci/node-pureimage","owner":"joshmarinacci","description":"Pure JS implementation of the HTML Canvas 2D drawing API","archived":false,"fork":false,"pushed_at":"2025-04-30T23:09:51.000Z","size":2396,"stargazers_count":817,"open_issues_count":16,"forks_count":89,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-05-04T02:38:16.168Z","etag":null,"topics":["canvas","drawing","javascript","png"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"RedHatSatellite/satellite-host-cve","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshmarinacci.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2014-11-12T18:35:09.000Z","updated_at":"2025-04-30T16:26:03.000Z","dependencies_parsed_at":"2024-09-02T02:01:10.648Z","dependency_job_id":"d8a1bdd1-0d46-4e02-af91-f6334b7ce8cc","html_url":"https://github.com/joshmarinacci/node-pureimage","commit_stats":{"total_commits":491,"total_committers":40,"mean_commits":12.275,"dds":0.5336048879837068,"last_synced_commit":"4ff986b3249fb36ec2152629001544feb73ee872"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshmarinacci%2Fnode-pureimage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshmarinacci%2Fnode-pureimage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshmarinacci%2Fnode-pureimage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshmarinacci%2Fnode-pureimage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshmarinacci","download_url":"https://codeload.github.com/joshmarinacci/node-pureimage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254052713,"owners_count":22006716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["canvas","drawing","javascript","png"],"created_at":"2024-08-01T21:01:40.478Z","updated_at":"2025-05-14T02:06:40.061Z","avatar_url":"https://github.com/joshmarinacci.png","language":"TypeScript","readme":"# PureImage\n\nPureImage is a pure 100% JavaScript implementation of the HTML Canvas 2D drawing API for NodeJS.\nIt has no native dependencies. You can use it to resize images, draw text, render badges,\nconvert to grayscale, or anything else you could do with the standard Canvas 2D API. It also has\nadditional APIs to save an image as PNG and JPEG.\n\n# Typescript Rewrite\n\nAs of version 0.4.\\* PureImage has been rewritten in 100% Typescript. The module is compiled\nto both Common JS and ES Modules. If it was working for you before it should still work, but\nif you notice anything off please file a bug report.\n\nAlso _note_ that `font.load()` now returns a promise instead of using a callback. If you\nneed synchronous support you can still fuse `font.loadSync()`.\n\n# Installation\n\n```bash\nnpm install pureimage\n```\n\n# Simple example\n\nMake a 100x100 image, fill with red, write to png file\n\n```javascript\nimport * as PImage from \"pureimage\";\nimport * as fs from \"fs\";\n\n// make image\nconst img1 = PImage.make(100, 100);\n\n// get canvas context\nconst ctx = img1.getContext(\"2d\");\n\n// fill with red\nctx.fillStyle = \"red\";\nctx.fillRect(0, 0, 100, 100);\n\n//write to 'out.png'\nPImage.encodePNGToStream(img1, fs.createWriteStream(\"out.png\"))\n  .then(() =\u003e {\n    console.log(\"wrote out the png file to out.png\");\n  })\n  .catch((e) =\u003e {\n    console.log(\"there was an error writing\");\n  });\n```\n\nresult\n\n![red square](./images/redsquare.png)\n\n## supported Canvas Features\n\n_note_: PureImage values portability and simplicity of implementation over speed. If you need\nmaximum performance you should use a different library backed by native code, such as [Node-Canvas](https://www.npmjs.com/package/canvas)\n\n- set pixels\n- stroke and fill paths (rectangles, lines, quadratic curves, bezier curves, arcs/circles)\n- copy and scale images (nearest neighbor)\n- import and export JPG and PNG from streams using promises\n- render basic text (no bold or italics yet)\n- anti-aliased strokes and fills\n- transforms\n- standard globalAlpha and rgba() alpha compositing\n- clip shapes\n\n# On the roadmap, but still missing\n\n- gradients fills\n- image fills\n- blend modes besides SRC OVER\n- smooth clip shapes\n- bold/italic fonts\n- smooth image interpolation\n\n# Why?\n\nThere are more than enough drawing APIs out there. Why do we need another? My\npersonal hatred of C/C++ compilers is [widely known](https://joshondesign.com/2014/09/17/rustlang).\nThe popular Node module [Canvas.js](https://github.com/Automattic/node-canvas) does a great\njob, but it's backed by Cairo, a C/C++ layer. I hate having native dependencies\nin Node modules. They often don't compile, or break after a system update. They\noften don't support non-X86 architectures (like the Raspberry Pi). You have\nto have a compiler already installed to use them, along with any other native\ndependencies pre-installed (like Cairo).\n\nSo, I made PureImage. Its goal is to implement the HTML Canvas spec in a headless\nNode buffer. No browser or window required.\n\nPureImage is meant to be a small and maintainable Canvas library.\nIt is _not meant to be fast_. If there are two choices of algorithm we will\ntake the one with the simplest implementation, and preferably the fewest lines.\nWe avoid special cases and optimizations to keep the code simple and maintainable.\nIt should run everywhere and be always produce the same output. But it will not be\nfast. If you need speed go use something else.\n\nPureImage uses only pure JS dependencies. [OpenType](https://github.com/nodebox/opentype.js/)\nfor font parsing, [PngJS](https://github.com/niegowski/node-pngjs) for PNG import/export,\nand [jpeg-js](https://github.com/eugeneware/jpeg-js) for JPG import/export.\n\n# Examples\n\nMake a new empty image, 100px by 50px. Automatically filled with 100% opaque black.\n\n```js\nvar PImage = require(\"pureimage\");\nvar img1 = PImage.make(100, 50);\n```\n\nFill with a red rectangle with 50% opacity\n\n```js\nvar ctx = img1.getContext(\"2d\");\nctx.fillStyle = \"rgba(255,0,0, 0.5)\";\nctx.fillRect(0, 0, 100, 100);\n```\n\nFill a green circle with a radius of 40 pixels in the middle of a 100px square black image.\n\n```js\nvar img = PImage.make(100, 100);\nvar ctx = img.getContext(\"2d\");\nctx.fillStyle = \"#00ff00\";\nctx.beginPath();\nctx.arc(50, 50, 40, 0, Math.PI * 2, true); // Outer circle\nctx.closePath();\nctx.fill();\n```\n\n![image of arcto with some fringing bugs](firstimages/arcto.png)\n\nDraw the string 'ABC' in white in the font 'Source Sans Pro', loaded from disk, at a size\nof 48 points.\n\n```js\ntest(\"font test\", (t) =\u003e {\n  var fnt = PImage.registerFont(\n    \"test/fonts/SourceSansPro-Regular.ttf\",\n    \"Source Sans Pro\",\n  );\n  fnt.loadSync();\n  var img = PImage.make(200, 200);\n  var ctx = img.getContext(\"2d\");\n  ctx.fillStyle = \"#ffffff\";\n  ctx.font = \"48pt 'Source Sans Pro'\";\n  ctx.fillText(\"ABC\", 80, 80);\n});\n```\n\nWrite out to a PNG file\n\n```js\nPImage.encodePNGToStream(img1, fs.createWriteStream(\"out.png\"))\n  .then(() =\u003e {\n    console.log(\"wrote out the png file to out.png\");\n  })\n  .catch((e) =\u003e {\n    console.log(\"there was an error writing\");\n  });\n```\n\nRead a jpeg, resize it, then save it out\n\n```js\nPImage.decodeJPEGFromStream(fs.createReadStream(\"test/images/bird.jpg\")).then(\n  (img) =\u003e {\n    console.log(\"size is\", img.width, img.height);\n    var img2 = PImage.make(50, 50);\n    var c = img2.getContext(\"2d\");\n    c.drawImage(\n      img,\n      0,\n      0,\n      img.width,\n      img.height, // source dimensions\n      0,\n      0,\n      50,\n      50, // destination dimensions\n    );\n    var pth = path.join(BUILD_DIR, \"resized_bird.jpg\");\n    PImage.encodeJPEGToStream(img2, fs.createWriteStream(pth), 50).then(() =\u003e {\n      console.log(\"done writing\");\n    });\n  },\n);\n```\n\nThis examples streams an image from a URL to a memory buffer, draws the current date in big black letters, and writes the final image to disk\n\n```javascript\nimport * as PImage from \"pureimage\";\nimport fs from \"fs\";\nimport * as client from \"https\";\nlet url =\n  \"https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png\";\nlet filepath = \"output_stream_sync.png\";\n//register font\nconst font = PImage.registerFont(\n  \"../test/unit/fixtures/fonts/SourceSansPro-Regular.ttf\",\n  \"MyFont\",\n);\n//load font\nfont.loadSync();\n//get image\nclient.get(url, (image_stream) =\u003e {\n  //decode image\n  PImage.decodePNGFromStream(image_stream).then((img) =\u003e {\n    //get context\n    const ctx = img.getContext(\"2d\");\n    ctx.fillStyle = \"#000000\";\n    ctx.font = \"60pt MyFont\";\n    ctx.fillText(new Date().toDateString(), 50, 80);\n    PImage.encodePNGToStream(img, fs.createWriteStream(filepath)).then(() =\u003e {\n      console.log(\"done writing to \", filepath);\n    });\n  });\n});\n```\n\nproduces\n\n![stream example](./images/streamexample.png)\n\nThe same as above but with Promises\n\n```javascript\nimport * as PImage from \"pureimage\";\nimport fs from \"fs\";\nimport * as client from \"https\";\n\nlet url =\n  \"https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png\";\nlet filepath = \"output.png\";\nlet fontpath = \"test/unit/fixtures/fonts/SourceSansPro-Regular.ttf\";\nPImage.registerFont(fontpath, \"MyFont\")\n  .loadPromise()\n  //Promise hack because https module doesn't support promises natively)\n  .then(() =\u003e new Promise((res) =\u003e client.get(url, res)))\n  .then((stream) =\u003e PImage.decodePNGFromStream(stream))\n  .then((img) =\u003e {\n    //get context\n    const ctx = img.getContext(\"2d\");\n    ctx.fillStyle = \"#000000\";\n    ctx.font = \"60pt MyFont\";\n    ctx.fillText(new Date().toDateString(), 50, 80);\n    return PImage.encodePNGToStream(img, fs.createWriteStream(filepath));\n  })\n  .then(() =\u003e {\n    console.log(\"done writing\", filepath);\n  });\n```\n\nThe same as above but with async await\n\n```javascript\nimport fs from \"fs\";\nimport * as https from \"https\";\nconst https_get_P = (url) =\u003e new Promise((res) =\u003e https.get(url, res));\n\nasync function doit() {\n  let url =\n    \"https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png\";\n  let filepath = \"output_stream_async.png\";\n  //register font\n  const font = PImage.registerFont(\n    \"../test/unit/fixtures/fonts/SourceSansPro-Regular.ttf\",\n    \"MyFont\",\n  );\n  //load font\n  await font.load();\n  //get image\n  let image_stream = await https_get_P(url);\n  //decode image\n  let img = await PImage.decodePNGFromStream(image_stream);\n  //get context\n  const ctx = img.getContext(\"2d\");\n  ctx.fillStyle = \"#000000\";\n  ctx.font = \"60pt MyFont\";\n  ctx.fillText(new Date().toDateString(), 50, 80);\n  await PImage.encodePNGToStream(img, fs.createWriteStream(filepath));\n  console.log(\"done writing to \", filepath);\n}\ndoit()\n  .then(() =\u003e console.log(\"done\"))\n  .catch((e) =\u003e console.error(e));\n```\n\nSave a canvas to a NodeJS buffer as PNG using a `PassThrough` stream:\n\n```javascript\nimport * as PImage from \"pureimage\";\nimport { PassThrough } from \"stream\";\nconst passThroughStream = new PassThrough();\nconst pngData = [];\npassThroughStream.on(\"data\", (chunk) =\u003e pngData.push(chunk));\npassThroughStream.on(\"end\", () =\u003e {});\npureimage.encodePNGToStream(canvas, passThroughStream).then(() =\u003e {\n  let buf = Buffer.concat(pngData);\n  expect(buf[0]).to.eq(0x89);\n  expect(buf[1]).to.eq(0x50);\n  expect(buf[2]).to.eq(0x4e);\n  expect(buf[3]).to.eq(0x47);\n  done();\n});\n```\n\nConvert a canvas to base64:\n\n```javascript\nimport * as PImage from \"pureimage\";\nimport { PassThrough } from \"stream\";\nconst passThroughStream = new PassThrough();\nconst pngData = [];\npassThroughStream.on(\"data\", (chunk) =\u003e pngData.push(chunk));\npassThroughStream.on(\"end\", () =\u003e {});\nawait pureimage.encodePNGToStream(canvas, passThroughStream);\nlet buf = Buffer.concat(pngData);\nlet b64 = buf.toString(\"base64\");\n```\n\n# Troubleshooting\n\n### missing or broken text\n\nPureImage uses [OpenType.js](https://opentype.js.org) to parse fonts\nand rasterize glyphs. If you are having trouble rendering something first\ncheck on the [OpenType website](https://opentype.js.org) that the font can\nactually be parsed and rendered. If you are rendering non-latin character sets\nyou may need to install an additional dependency to your operating system.\nFor example, rendering arabic text may require `pip install arabic-reshaper` on Linux.\n\n### Using a really large image buffer\n\nPureImage has no inherit size limitations, but NodeJS does have a default max memory\nsetting. You can learn how to increase the default [here](https://stackoverflow.com/questions/34356012/how-to-increase-nodejs-default-memory)\n\n# New 0.4.x release\n\nAfter another long lull, I've ported PureImage to Typescript. Most of the work\nwas actually done by the amazing and talented [Josh Hemphill](https://github.com/josh-hemphill).\nAs part of this port I switched to using [esbuild](https://esbuild.github.io) for compiling\n\u0026 packaging the Typescript, and [Vitest](https://vitest.dev) for unit tests.\nThey are vastly faster than our old system.\n\nThis release also fixes tons of bugs and adds some small features:\n\n- updated PngJS, OpenType.jS and JPegJS to their latest version.\n- Node 14 is now the minimum supported version\n- linear and radial gradient fills are supported. See [test/gradientfill.test.ts](test/gradientfill.test.ts))\n\n# New 0.3.x release\n\nAfter a long lull, I've ported the code to modern ES6 modules, so you can just do an\n`import pureimage from 'pureimage'` like any other proper modern module. If you are using\n`require('pureimage')` it should just work thanks to the `dist/pureimage-umd.cjs` file built\nwith [Rollup](https://rollupjs.org). It also has a stub to let `pureimage` run in the browser and delegate to the\nreal HTML canvas. This helps with isomorphic apps.\n\nOther updates include\n\n- Switch to [MochaJS](https://mochajs.org) for the unit tests.\n- add more unit tests.\n- [support](https://github.com/joshmarinacci/node-pureimage/issues/117) drawing images when using transforms\n- [implement](https://github.com/joshmarinacci/node-pureimage/issues/100) `rect()`\n- implement ImageData with `getImageData()` and `putImageData()`\n- fix gradient fill\n- [add all](https://github.com/joshmarinacci/node-pureimage/commit/ba975575ca986ea11c427082d88833fb153e779d) CSS named colors\n- [support](https://github.com/joshmarinacci/node-pureimage/pull/108) #rgb, #rgba, and #rrggbbaa color strings\n- applied more bug fixes from PRs, thanks to our contributors.\n\n# New 0.1.x release\n\nI've completely refactored the code so that it should be easier to\nmaintain and implement new features. For the most part there are no API changes (since the API is\ndefined by the HTML Canvas spec), but if you\nwere using the font or image loading extensions\nyou will need to use the new function names and switch to promises. For more information, please see [the API docs](http://joshmarinacci.github.io/node-pureimage)\n\nI'm also using Node buffers instead of arrays internally, so you can work with large images\nfaster than before. Rich text is no longer supported, which is fine because it never really worked\nanyway. We'll have to find a different way to do it.\n\nI've tried to maintain all of the patches that have been sent in, but if you contributed a patch\nplease check that it still works. Thank you all! - josh\n\n# Thanks!\n\nThanks to Nodebox / EMRG for [opentype.js](https://github.com/nodebox/opentype.js/)\n\nThanks to Rosetta Code for [Bresenham's in JS](http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#JavaScript)\n\nThanks to Kuba Niegowski for [PngJS](https://github.com/niegowski/node-pngjs)\n\nThanks to Eugene Ware for [jpeg-js](https://github.com/eugeneware/jpeg-js)\n\nThanks for patches from:\n\n- Dan [danielbarela](https://github.com/danielbarela)\n- Eugene Kulabuhov [ekulabuhov](https://github.com/ekulabuhov)\n- Lethexa [lethexa](https://github.com/lethexa)\n- The Louie [the-louie](https://github.com/the-louie)\n- Jan Marsch [kekscom](https://github.com/kekscom)\n","funding_links":[],"categories":["javascript","JavaScript","目录","TypeScript","Packages","graphic (图形库)"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshmarinacci%2Fnode-pureimage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshmarinacci%2Fnode-pureimage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshmarinacci%2Fnode-pureimage/lists"}