Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: joshmarinacci/node-pureimage
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 062227213e6664634886fbeb00e5184059f5ce60
Choose a base ref
...
head repository: joshmarinacci/node-pureimage
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2071c5544a9fa6ad14e57b6b7ae29f7f2530b68d
Choose a head ref

Commits on Mar 8, 2021

  1. fix up some tests

    Josh Marinacci committed Mar 8, 2021
    Copy the full SHA
    6346eb1 View commit details
  2. initial work on switching to mocha as es6 modules

    Josh Marinacci committed Mar 8, 2021
    Copy the full SHA
    83be5eb View commit details
  3. more unit tests

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    329b873 View commit details
  4. fix up line test

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    f17e7fb View commit details
  5. get text tests workin

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    30b3546 View commit details
  6. Copy the full SHA
    44b0799 View commit details
  7. make path test work

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    e1a3a66 View commit details
  8. Copy the full SHA
    ecc9a0d View commit details
  9. Copy the full SHA
    896422a View commit details
  10. activate tests

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    b03cec2 View commit details
  11. Copy the full SHA
    d289c33 View commit details
  12. fix more warnings

    remove some dead code
    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    f62fd04 View commit details
  13. change some names

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    b5efa0d View commit details
  14. small cleanups

    joshmarinacci committed Mar 8, 2021
    Copy the full SHA
    d025c0f View commit details

Commits on Mar 29, 2021

  1. Copy the full SHA
    837cfa4 View commit details
  2. Copy the full SHA
    13b09c0 View commit details
  3. cleanup

    joshmarinacci committed Mar 29, 2021
    Copy the full SHA
    e239e1d View commit details
  4. Copy the full SHA
    4574df9 View commit details

Commits on Mar 30, 2021

  1. create bounds class

    implement proper image transform drawing
    fix for #117
    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    b7bb6c3 View commit details
  2. Copy the full SHA
    339c96f View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4a0af0e View commit details
  4. Copy the full SHA
    3c56a9f View commit details
  5. 0.3.0

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    b056758 View commit details
  6. fix png import

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    32dc2f2 View commit details
  7. 0.3.1

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    a27bbc0 View commit details
  8. fix umd imports

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    f776e59 View commit details
  9. 0.3.2

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    9cf942a View commit details
  10. fix ignores

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    0e29fb5 View commit details
  11. update readme

    joshmarinacci committed Mar 30, 2021
    Copy the full SHA
    1ff8d46 View commit details

Commits on Apr 3, 2021

  1. add type declaration files

    JeanJPNM committed Apr 3, 2021
    Copy the full SHA
    6740764 View commit details
  2. fix errors

    JeanJPNM committed Apr 3, 2021
    Copy the full SHA
    ceb6f99 View commit details

Commits on Apr 16, 2021

  1. Merge pull request #119 from JeanJPNM/add-declaration-types

    Add declaration types
    joshmarinacci authored Apr 16, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e7733c6 View commit details

Commits on Jun 9, 2021

  1. remove travis build button

    removed the travis build button since we don't use it anymore.
    joshmarinacci authored Jun 9, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a83ca6d View commit details

Commits on Jun 26, 2021

  1. Copy the full SHA
    ae99521 View commit details

Commits on Jun 29, 2021

  1. Copy the full SHA
    5bec2ae View commit details
  2. Copy the full SHA
    539058e View commit details
  3. Copy the full SHA
    a8f109d View commit details
  4. update examples

    joshmarinacci committed Jun 29, 2021
    Copy the full SHA
    70fd409 View commit details

Commits on Jul 9, 2021

  1. Copy the full SHA
    9dab754 View commit details
  2. Copy the full SHA
    1c072eb View commit details
  3. Copy the full SHA
    00de1ea View commit details
  4. restore the old fillstyle after drawing thick stroked lines

    refuse to draw lines with NaN or bigger than MaxInt
    skip points in a path when they are the same as the previous point
    add Line.is_invalid()
    add Point.isEquals()
    joshmarinacci committed Jul 9, 2021
    Copy the full SHA
    ea400fc View commit details

Commits on Aug 18, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c9d788f View commit details

Commits on Aug 23, 2021

  1. 0.3.3

    joshmarinacci committed Aug 23, 2021
    Copy the full SHA
    79732f8 View commit details
  2. 0.3.3

    joshmarinacci committed Aug 23, 2021
    Copy the full SHA
    451aaff View commit details
  3. 0.3.4

    joshmarinacci committed Aug 23, 2021
    Copy the full SHA
    b53a33e View commit details
  4. 0.3.5

    joshmarinacci committed Aug 23, 2021
    Copy the full SHA
    b957f55 View commit details

Commits on Oct 1, 2021

  1. Copy the full SHA
    e2348c6 View commit details

Commits on Oct 19, 2021

  1. Merge pull request #122 from voxpelli/add-drawimage-overload-types

    Add `drawImage` overloads to types
    joshmarinacci authored Oct 19, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a9f7d3d View commit details

Commits on Nov 10, 2021

  1. Copy the full SHA
    f33e298 View commit details
Showing with 16,970 additions and 15,164 deletions.
  1. +2 −1 .gitignore
  2. +4 −0 .npmignore
  3. +140 −21 README.md
  4. BIN images/redsquare.png
  5. BIN images/streamexample.png
  6. +4,582 −14,302 package-lock.json
  7. +15 −25 package.json
  8. +16 −0 rollup.config.js
  9. +0 −50 src/Point.js
  10. +11 −12 src/bitmap.js
  11. +337 −168 src/context.js
  12. +13 −18 src/{Gradient.js → gradients.js}
  13. +63 −72 src/{pureimage.js → index.js}
  14. +14 −3 src/{Line.js → line.js}
  15. +151 −152 src/named_colors.js
  16. +115 −0 src/point.js
  17. +24 −18 src/text.js
  18. +37 −30 src/transform.js
  19. +49 −81 src/uint32.js
  20. +6 −4 src/util.js
  21. +38 −0 test/bugs/bug_129.html
  22. +36 −0 test/bugs/bug_129.js
  23. +62 −0 test/clipping.test.js
  24. +7 −4 {tests/unit/specs → test}/color.test.js
  25. +19 −0 test/common.js
  26. +14 −12 {tests/unit/specs → test}/drawimage.test.js
  27. +12 −0 test/examples/make_image.js
  28. +32 −0 test/examples/stream_example.js
  29. +34 −0 test/examples/stream_promise_example.js
  30. +11 −6 {tests/unit/specs → test}/gradientfill.test.js
  31. +14 −12 {tests/unit/specs → test}/imagedata.test.js
  32. +12 −10 {tests/unit/specs → test}/line.test.js
  33. +104 −0 test/mathtest.js
  34. 0 {tests → test/old}/curves.html
  35. 0 {tests → test/old}/horse.html
  36. +70 −22 {tests/unit/specs → test}/path.test.js
  37. +4 −3 {tests/unit/specs → test}/point.test.js
  38. +33 −38 tests/unit/specs/pureimage.test.js → test/readwrite.test.js
  39. +24 −25 {tests/unit/specs → test}/text.test.js
  40. +168 −0 test/transforms.js
  41. BIN {tests → test}/unit/fixtures/fonts/SourceSansPro-Italic.ttf
  42. BIN {tests → test}/unit/fixtures/fonts/SourceSansPro-Regular.ttf
  43. BIN {tests → test}/unit/fixtures/images/bird.jpg
  44. BIN {tests → test}/unit/fixtures/images/bird.png
  45. BIN {tests → test}/unit/fixtures/images/test.jpg
  46. +3 −3 {tests → test}/unit/matchers/toBeOfFileType.js
  47. +9 −7 {tests → test}/unit/runtests.js
  48. BIN test/weather/OpenSans-Bold.ttf
  49. BIN test/weather/OpenSans-Regular.ttf
  50. BIN test/weather/alata-regular.ttf
  51. +8,692 −0 test/weather/data.json
  52. +401 −0 test/weather/render.js
  53. +55 −0 test/weather/weatherdata.js
  54. +0 −65 tests/unit/transforms.js
  55. +109 −0 types/bitmap.d.ts
  56. +1 −0 types/browser.d.ts
  57. +771 −0 types/context.d.ts
  58. +26 −0 types/gradients.d.ts
  59. +57 −0 types/index.d.ts
  60. +42 −0 types/line.d.ts
  61. +156 −0 types/named_colors.d.ts
  62. +45 −0 types/point.d.ts
  63. +73 −0 types/text.d.ts
  64. +71 −0 types/transform.d.ts
  65. +154 −0 types/uint32.d.ts
  66. +32 −0 types/util.d.ts
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ node_modules
build
.idea
docs
tests/unit/coverage
test/unit/coverage
.vscode
output
dist
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
*.png
test
tests
161 changes: 140 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,50 @@
[![Build Status](https://travis-ci.org/joshmarinacci/node-pureimage.svg?branch=master)](https://travis-ci.org/joshmarinacci/node-pureimage)

PureImage
==============

PureImage is a pure JavaScript implementation of the HTML Canvas 2d drawing api for NodeJS.
It has no native dependencies.
PureImage is a pure 100% JavaScript implementation of the HTML Canvas 2D drawing API for NodeJS.
It has no native dependencies. You can use it to resize images, draw text, render badges,
convert to grayscale, or anything else you could do with the standard Canvas 2D API.

Simple example
==============

New 0.1.x release
=================
Make a 100x100 image, fill with red, write to png file

I've completely refactored the code so that it should be easier to
maintain and implement new features. For the most part there are no API changes (since the API is
defined by the HTML Canvas spec), but if you
were using the font or image loading extensions
you 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)
```javascript
import * as PImage from "pureimage"
import * as fs from 'fs'

I'm also using Node buffers instead of arrays internally, so you can work with large images
faster than before. Rich text is no longer supported, which is fine because it never really worked
anyway. We'll have to find a different way to do it.
// make image
const img1 = PImage.make(100, 100)

I've tried to maintain all of the patches that have been sent in, but if you contributed a patch
please check that it still works. Thank you all! - josh
// get canvas context
const ctx = img1.getContext('2d');

// fill with red
ctx.fillStyle = 'red';
ctx.fillRect(0,0,100,100);

//write to 'out.png'
PImage.encodePNGToStream(img1, fs.createWriteStream('out.png')).then(() => {
console.log("wrote out the png file to out.png");
}).catch((e)=>{
console.log("there was an error writing");
});

```

result

![red square](./images/redsquare.png)


## supported Canvas Features

*note*: PureImage values portability and simplicity of implementation over speed. If you need
maximum performance you should use a different library backed by native code, such as [Node-Canvas](https://www.npmjs.com/package/canvas)


* set pixels
* stroke and fill paths (rectangles, lines, quadratic curves, bezier curves, arcs/circles)
* copy and scale images (nearest neighbor)
@@ -46,7 +64,6 @@ On the roadmap, but still missing
* blend modes besides SRC OVER
* smooth clip shapes
* bold/italic fonts
* measure text
* smooth image interpolation


@@ -76,9 +93,6 @@ PureImage uses only pure JS dependencies. [OpenType](https://github.com/nodebox
for font parsing, [PngJS](https://github.com/niegowski/node-pngjs) for PNG import/export,
and [jpeg-js](https://github.com/eugeneware/jpeg-js) for JPG import/export.

Documentation
=============
Documentation can now be found at: http://joshmarinacci.github.io/node-pureimage

Examples
=========
@@ -118,7 +132,7 @@ of 48 points.

```js
test('font test', (t) => {
var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
var fnt = PImage.registerFont('test/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
fnt.load(() => {
var img = PImage.make(200,200);
var ctx = img.getContext('2d');
@@ -143,7 +157,7 @@ PImage.encodePNGToStream(img1, fs.createWriteStream('out.png')).then(() => {
Read a jpeg, resize it, then save it out

```js
PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img) => {
PImage.decodeJPEGFromStream(fs.createReadStream("test/images/bird.jpg")).then((img) => {
console.log("size is",img.width,img.height);
var img2 = PImage.make(50,50);
var c = img2.getContext('2d');
@@ -158,6 +172,111 @@ PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((
});
```

This 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

```javascript
import * as PImage from "pureimage"
import fs from 'fs'
import * as client from "https"

let url = "https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png"
let filepath = "output.png"
//register font
const font = PImage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf','MyFont');
//load font
font.load(() => {
//get image
client.get(url, (image_stream)=>{
//decode image
PImage.decodePNGFromStream(image_stream).then(img => {
//get context
const ctx = img.getContext('2d');
ctx.fillStyle = '#000000';
ctx.font = "60pt MyFont";
ctx.fillText(new Date().toDateString(), 50, 80);
PImage.encodePNGToStream(img, fs.createWriteStream(filepath)).then(()=>{
console.log("done writing to ",filepath)
})
});
})
})
```

produces
![stream example](./images/streamexample.png)

The same as above but with Promises

```javascript
import * as PImage from "pureimage"
import fs from 'fs'
import * as client from "https"

let url = "https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png"
let filepath = "output.png"
let fontpath = 'test/unit/fixtures/fonts/SourceSansPro-Regular.ttf'
PImage.registerFont(fontpath,'MyFont').loadPromise()
//Promise hack because https module doesn't support promises natively)
.then(()=>new Promise(res => client.get(url,res)))
.then(stream => PImage.decodePNGFromStream(stream))
.then(img => {
//get context
const ctx = img.getContext('2d');
ctx.fillStyle = '#000000';
ctx.font = "60pt MyFont";
ctx.fillText(new Date().toDateString(), 50, 80);
return PImage.encodePNGToStream(img, fs.createWriteStream(filepath))
})
.then(()=>{
console.log('done writing',filepath)
})
```


New 0.3.x release
=================

After a long lull, I've ported the code to modern ES6 modules, so you can just do an
`import pureimage from 'pureimage'` like any other proper modern module. If you are using
`require('pureimage')` it should just work thanks to the `dist/pureimage-umd.cjs` file built
with [Rollup](https://rollupjs.org). It also has a stub to let `pureimage` run in the browser and delegate to the
real HTML canvas. This helps with isomorphic apps.

Other updates include

* Switch to [MochaJS](https://mochajs.org) for the unit tests.
* add more unit tests.
* [support](https://github.com/joshmarinacci/node-pureimage/issues/117) drawing images when using transforms
* [implement](https://github.com/joshmarinacci/node-pureimage/issues/100) `rect()`
* implement ImageData with `getImageData()` and `putImageData()`
* fix gradient fill
* [add all](https://github.com/joshmarinacci/node-pureimage/commit/ba975575ca986ea11c427082d88833fb153e779d) CSS named colors
* [support](https://github.com/joshmarinacci/node-pureimage/pull/108) #rgb, #rgba, and #rrggbbaa color strings
* applied more bug fixes from PRs, thanks to our contributors.





New 0.1.x release
=================

I've completely refactored the code so that it should be easier to
maintain and implement new features. For the most part there are no API changes (since the API is
defined by the HTML Canvas spec), but if you
were using the font or image loading extensions
you 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)

I'm also using Node buffers instead of arrays internally, so you can work with large images
faster than before. Rich text is no longer supported, which is fine because it never really worked
anyway. We'll have to find a different way to do it.

I've tried to maintain all of the patches that have been sent in, but if you contributed a patch
please check that it still works. Thank you all! - josh






Thanks!
Binary file added images/redsquare.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/streamexample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18,884 changes: 4,582 additions & 14,302 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 15 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"name": "pureimage",
"version": "0.2.7",
"version": "0.3.8",
"description": "Pure JS image drawing API based on Canvas. Export to PNG streams.",
"author": {
"name": "Josh Marinacci",
"email": "joshua@marinacci.org",
"url": "http://joshondesign.com/"
},
"main": "./src/pureimage.js",
"type": "module",
"types": "./types/index.d.ts",
"module": "./src/index.js",
"main": "./dist/pureimage-umd.cjs",
"browser": "./src/browser.js",
"repository": {
"type": "git",
@@ -19,37 +22,24 @@
"pngjs": "^3.3.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"chai": "^4.3.3",
"esdoc": "^1.1.0",
"esdoc-integrate-test-plugin": "^1.0.0",
"esdoc-member-plugin": "^1.0.0",
"esdoc-node": "^1.0.3",
"esdoc-standard-plugin": "^1.0.0",
"file-type": "^7.6.0",
"jest": "^22.4.2"
"mocha": "^8.3.1",
"rollup": "^2.44.0",
"tape": "^5.2.2"
},
"scripts": {
"unit": "jest",
"docs": "esdoc"
},
"jest": {
"collectCoverage": true,
"coverageDirectory": "./tests/unit/coverage",
"coverageReporters": [
"lcov",
"text"
],
"collectCoverageFrom": [
"src/**/*.{js}"
],
"modulePaths": [
"<rootDir>/src/"
],
"notify": true,
"testEnvironment": "node",
"globals": {
"FIXTURES_DIR": "./tests/unit/fixtures/"
},
"verbose": true
"test": "mocha",
"docs": "esdoc",
"build": "rollup -c",
"clean": "rm -rf dist/ output/ *.tgz"
},
"engines": {
"node": ">=0.8"
16 changes: 16 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

// https://rollupjs.org/guide/en/#configuration-files
export default {
input: 'src/index.js',
output: {
file: 'dist/pureimage-umd.cjs',
format: 'umd',
name: 'PureImage'
},
plugins: [
resolve(),
commonjs()
]
};
50 changes: 0 additions & 50 deletions src/Point.js

This file was deleted.

23 changes: 11 additions & 12 deletions src/bitmap.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const Context = require('./context');
const NAMED_COLORS = require('./named_colors');
const uint32 = require('./uint32');
import {NAMED_COLORS} from "./named_colors.js"
import {Context} from "./context.js"
import {fromBytesBigEndian, getBytesBigEndian} from './uint32.js'

/**
* The Bitmap class is used for direct pixel manipulation(for example setting a pixel colour,
@@ -9,7 +9,7 @@ const uint32 = require('./uint32');
*
* @class Bitmap
*/
class Bitmap {
export class Bitmap {

/**
* Creates an instance of Bitmap.
@@ -36,8 +36,8 @@ class Bitmap {
this.data = Buffer.alloc(w*h*4);

const fillval = NAMED_COLORS.transparent
for(var j=0; j<h; j++) {
for (var i = 0; i < w; i++) {
for(let j=0; j<h; j++) {
for (let i = 0; i < w; i++) {
this.setPixelRGBA(i, j, fillval);
}
}
@@ -74,7 +74,7 @@ class Bitmap {
*/
setPixelRGBA(x,y,rgba) {
let i = this.calculateIndex(x, y);
const bytes = uint32.getBytesBigEndian(rgba);
const bytes = getBytesBigEndian(rgba);
this.data[i+0] = bytes[0];
this.data[i+1] = bytes[1];
this.data[i+2] = bytes[2];
@@ -115,7 +115,7 @@ class Bitmap {
*/
getPixelRGBA(x,y) {
let i = this.calculateIndex(x, y);
return uint32.fromBytesBigEndian(
return fromBytesBigEndian(
this.data[i+0],
this.data[i+1],
this.data[i+2],
@@ -135,7 +135,7 @@ class Bitmap {
* @memberof Bitmap
*/
getPixelRGBA_separate(x,y) {
var i = this.calculateIndex(x,y);
const i = this.calculateIndex(x, y)
return this.data.slice(i,i+4);
}

@@ -146,12 +146,12 @@ class Bitmap {
*
* @memberof Bitmap
*/
getContext() {
getContext(type) {
return new Context(this);
}

_copySubBitmap(x,y,w,h) {
let dst = new Bitmap(w,h)
let dst = new Bitmap(w,h,{})
for(let i=0; i<w; i++) {
for(let j=0; j<h; j++) {
let indexA = this.calculateIndex(x+i,y+j)
@@ -177,4 +177,3 @@ class Bitmap {
}

}
module.exports = Bitmap;
505 changes: 337 additions & 168 deletions src/context.js

Large diffs are not rendered by default.

31 changes: 13 additions & 18 deletions src/Gradient.js → src/gradients.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
const Point = require('./Point')
const uint32 = require('./uint32')
const util = require('./util')
import {Point} from "./point.js"
import {fromBytesBigEndian, getBytesBigEndian} from './uint32.js'
import {clamp, colorStringToUint32, lerp} from './util.js'

class CanvasGradient {
export class CanvasGradient {
constructor() {
this.stops = []
}
addColorStop(t,colorstring) {
const color = util.colorStringToUint32(colorstring)
const color = colorStringToUint32(colorstring)
this.stops.push({t:t,color:color})
}
_lerpStops(t) {
const first = uint32.getBytesBigEndian(this.stops[0].color).map(b=>b/255);
const second = uint32.getBytesBigEndian(this.stops[1].color).map(b=>b/255);
const fc = first.map((f,i) => util.lerp(f,second[i],t)).map(c=>c*255)
return uint32.fromBytesBigEndian(fc[0],fc[1],fc[2],0xFF)
const first = getBytesBigEndian(this.stops[0].color).map(b=>b/255);
const second = getBytesBigEndian(this.stops[1].color).map(b=>b/255);
const fc = first.map((f,i) => lerp(f,second[i],t)).map(c=>c*255)
return fromBytesBigEndian(fc[0],fc[1],fc[2],0xFF)
}
}

class LinearGradient extends CanvasGradient {
export class LinearGradient extends CanvasGradient {
constructor(x0,y0,x1,y1) {
super()
this.start = new Point(x0,y0)
@@ -37,13 +37,13 @@ class LinearGradient extends CanvasGradient {
//project V0 onto V
let t = V0.dotProduct(V)
//convert to t value and clamp
t = util.clamp(t/d,0,1)
t = clamp(t/d,0,1)
return this._lerpStops(t)
}
}


class RadialGradient extends CanvasGradient {
export class RadialGradient extends CanvasGradient {
constructor(x0, y0, x1, y1) {
super()
this.start = new Point(x0,y0)
@@ -52,13 +52,8 @@ class RadialGradient extends CanvasGradient {
colorAt(x,y) {
const pc = new Point(x, y) //convert to a point
const dist = pc.distance(this.start)
let t = util.clamp(dist/10,0,1)
let t = clamp(dist/10,0,1)
return this._lerpStops(t)
}
}

module.exports = {
CanvasGradient: CanvasGradient,
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
}
135 changes: 63 additions & 72 deletions src/pureimage.js → src/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
const Bitmap = require('./bitmap');
const fs = require('fs');
const JPEG = require('jpeg-js');
const PNG_LIB = require('pngjs')
const PNG = PNG_LIB.PNG;
const text = require('./text');
const uint32 = require('./uint32');
import {Bitmap} from './bitmap.js'
import {PNG} from "pngjs"
import * as JPEG from "jpeg-js"
import {getBytesBigEndian} from './uint32.js'

/**
* Create a new bitmap image
*
* @param {number} w Image width
* @param {number} h Image height
* @param {object} options Options to be passed to {@link Bitmap}
*
* @returns {Bitmap}
*/
exports.make = function(w,h,options) {
return new Bitmap(w,h,options);
};
export * from './text.js'

export function make (w,h,options) {
return new Bitmap(w,h,options)
}

/**
* Encode the PNG image to output stream
@@ -27,22 +17,23 @@ exports.make = function(w,h,options) {
*
* @returns {Promise<void>}
*/
exports.encodePNGToStream = function(bitmap, outstream) {
export function encodePNGToStream(bitmap, outstream) {
return new Promise((res,rej)=>{
if(!bitmap.hasOwnProperty('data') || !bitmap.hasOwnProperty('width') || !bitmap.hasOwnProperty('height')) {
rej(new TypeError('Invalid bitmap image provided'));
return rej(new TypeError('Invalid bitmap image provided'));
}
var png = new PNG({
width:bitmap.width,
height:bitmap.height
});

for(var i=0; i<bitmap.width; i++) {
for(var j=0; j<bitmap.height; j++) {
var rgba = bitmap.getPixelRGBA(i,j);
var n = (j*bitmap.width+i)*4;
var bytes = uint32.getBytesBigEndian(rgba);
for(var k=0; k<4; k++) {
const png = new PNG({
width: bitmap.width,
height: bitmap.height
})

for(let i=0; i<bitmap.width; i++) {
for(let j=0; j<bitmap.height; j++) {
const rgba = bitmap.getPixelRGBA(i, j)
const n = (j * bitmap.width + i) * 4
const bytes = getBytesBigEndian(rgba)
for(let k=0; k<4; k++) {
png.data[n+k] = bytes[k];
}
}
@@ -57,6 +48,32 @@ exports.encodePNGToStream = function(bitmap, outstream) {
});
}


/**
* Decode PNG From Stream
*
* Decode a PNG file from an incoming readable stream
*
* @param {Stream} instream A readable stream containing raw PNG data
*
* @returns {Promise<Bitmap>}
*/
export function decodePNGFromStream(instream) {
return new Promise((res,rej)=>{
instream.pipe(new PNG())
.on("parsed", function() {
const bitmap = new Bitmap(this.width, this.height,{})
for(let i=0; i<bitmap.data.length; i++) {
bitmap.data[i] = this.data[i];
}
res(bitmap);
}).on("error", function(err) {
rej(err);
});
})
};


/**
* Encode JPEG To Stream
*
@@ -67,24 +84,24 @@ exports.encodePNGToStream = function(bitmap, outstream) {
* @param {Int} Number between 0 and 100 setting the JPEG quality
* @returns {Promise<void>}
*/
exports.encodeJPEGToStream = function(img, outstream, quality) {
export function encodeJPEGToStream(img, outstream, quality) {
quality = quality || 90;
return new Promise((res,rej)=> {
if(!img.hasOwnProperty('data') || !img.hasOwnProperty('width') || !img.hasOwnProperty('height')) {
rej(new TypeError('Invalid bitmap image provided'));
return rej(new TypeError('Invalid bitmap image provided'));
}
var data = {
const data = {
data: img.data,
width: img.width,
height: img.height
};
}
outstream.on('error', (err) => rej(err));
outstream.write(JPEG.encode(data, quality).data, () => {
outstream.end();
res();
});
});
};
}

/**
* Decode JPEG From Stream
@@ -95,23 +112,24 @@ exports.encodeJPEGToStream = function(img, outstream, quality) {
*
* @returns {Promise<Bitmap>}
*/
exports.decodeJPEGFromStream = function(data) {
export function decodeJPEGFromStream(data) {
return new Promise((res,rej)=>{
try {
var chunks = [];
const chunks = []
data.on('data', chunk => chunks.push(chunk));
data.on('end',() => {
var buf = Buffer.concat(chunks);
const buf = Buffer.concat(chunks)
let rawImageData = null
try {
var rawImageData = JPEG.decode(buf);
rawImageData = JPEG.decode(buf);
} catch(err) {
rej(err);
return
}
var bitmap = new Bitmap(rawImageData.width, rawImageData.height);
for (var x_axis = 0; x_axis < rawImageData.width; x_axis++) {
for (var y_axis = 0; y_axis < rawImageData.height; y_axis++) {
var n = (y_axis * rawImageData.width + x_axis) * 4;
const bitmap = new Bitmap(rawImageData.width, rawImageData.height,{})
for (let x_axis = 0; x_axis < rawImageData.width; x_axis++) {
for (let y_axis = 0; y_axis < rawImageData.height; y_axis++) {
const n = (y_axis * rawImageData.width + x_axis) * 4
bitmap.setPixelRGBA_i(x_axis, y_axis,
rawImageData.data[n + 0],
rawImageData.data[n + 1],
@@ -129,32 +147,5 @@ exports.decodeJPEGFromStream = function(data) {
console.log(e);
rej(e);
}
});
};

/**
* Decode PNG From Stream
*
* Decode a PNG file from an incoming readable stream
*
* @param {Stream} instream A readable stream containing raw PNG data
*
* @returns {Promise<Bitmap>}
*/
exports.decodePNGFromStream = function(instream) {
return new Promise((res,rej)=>{
instream.pipe(new PNG())
.on("parsed", function() {
var bitmap = new Bitmap(this.width,this.height);
for(var i=0; i<bitmap.data.length; i++) {
bitmap.data[i] = this.data[i];
};
res(bitmap);
}).on("error", function(err) {
rej(err);
});
})
};

/**@ignore */
exports.registerFont = text.registerFont;
}
17 changes: 14 additions & 3 deletions src/Line.js → src/line.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Point = require('./Point');
import {Point} from "./point.js"

/**
* Create a line object represnting a set of two points in 2D space.
@@ -8,7 +8,7 @@ const Point = require('./Point');
*
* @class Line
*/
class Line {
export class Line {
/**
* Construct a Line using two {@link Point} objects
* .
@@ -66,7 +66,18 @@ class Line {
Math.pow(this.start.x - this.end.x, 2) + Math.pow(this.start.y - this.end.y, 2)
);
}

is_invalid() {
if(Number.isNaN(this.start.x)) return true
if(Number.isNaN(this.end.x)) return true
if(Number.isNaN(this.start.y)) return true
if(Number.isNaN(this.end.y)) return true
if(this.start.x > Number.MAX_SAFE_INTEGER) return true
if(this.start.y > Number.MAX_SAFE_INTEGER) return true
if(this.end.x > Number.MAX_SAFE_INTEGER) return true
if(this.end.y > Number.MAX_SAFE_INTEGER) return true
return false
}
}

/** @ignore */
module.exports = Line;
303 changes: 151 additions & 152 deletions src/named_colors.js
Original file line number Diff line number Diff line change
@@ -2,156 +2,155 @@
* Enumeration containing popular colors
* @enum {string}
*/
var NAMED_COLORS = {
transparent: 0x00000000,
aliceblue: 0xf0f8ffff,
antiquewhite: 0xfaebd7ff,
aqua: 0x00ffffff,
aquamarine: 0x7fffd4ff,
azure: 0xf0ffffff,
beige: 0xf5f5dcff,
bisque: 0xffe4c4ff,
black: 0x000000ff,
blanchedalmond: 0xffebcdff,
blue: 0x0000ffff,
blueviolet: 0x8a2be2ff,
brown: 0xa52a2aff,
burlywood: 0xdeb887ff,
cadetblue: 0x5f9ea0ff,
chartreuse: 0x7fff00ff,
chocolate: 0xd2691eff,
coral: 0xff7f50ff,
cornflowerblue: 0x6495edff,
cornsilk: 0xfff8dcff,
crimson: 0xdc143cff,
cyan: 0x00ffffff,
darkblue: 0x00008bff,
darkcyan: 0x008b8bff,
darkgoldenrod: 0xb8860bff,
darkgray: 0xa9a9a9ff,
darkgreen: 0x006400ff,
darkgrey: 0xa9a9a9ff,
darkkhaki: 0xbdb76bff,
darkmagenta: 0x8b008bff,
darkolivegreen: 0x556b2fff,
darkorange: 0xff8c00ff,
darkorchid: 0x9932ccff,
darkred: 0x8b0000ff,
darksalmon: 0xe9967aff,
darkseagreen: 0x8fbc8fff,
darkslateblue: 0x483d8bff,
darkslategray: 0x2f4f4fff,
darkslategrey: 0x2f4f4fff,
darkturquoise: 0x00ced1ff,
darkviolet: 0x9400d3ff,
deeppink: 0xff1493ff,
deepskyblue: 0x00bfffff,
dimgray: 0x696969ff,
dimgrey: 0x696969ff,
dodgerblue: 0x1e90ffff,
firebrick: 0xb22222ff,
floralwhite: 0xfffaf0ff,
forestgreen: 0x228b22ff,
fuchsia: 0xff00ffff,
gainsboro: 0xdcdcdcff,
ghostwhite: 0xf8f8ffff,
gold: 0xffd700ff,
goldenrod: 0xdaa520ff,
gray: 0x808080ff,
green: 0x008000ff,
greenyellow: 0xadff2fff,
grey: 0x808080ff,
honeydew: 0xf0fff0ff,
hotpink: 0xff69b4ff,
indianred: 0xcd5c5cff,
indigo: 0x4b0082ff,
ivory: 0xfffff0ff,
khaki: 0xf0e68cff,
lavender: 0xe6e6faff,
lavenderblush: 0xfff0f5ff,
lawngreen: 0x7cfc00ff,
lemonchiffon: 0xfffacdff,
lightblue: 0xadd8e6ff,
lightcoral: 0xf08080ff,
lightcyan: 0xe0ffffff,
lightgoldenrodyellow: 0xfafad2ff,
lightgray: 0xd3d3d3ff,
lightgreen: 0x90ee90ff,
lightgrey: 0xd3d3d3ff,
lightpink: 0xffb6c1ff,
lightsalmon: 0xffa07aff,
lightseagreen: 0x20b2aaff,
lightskyblue: 0x87cefaff,
lightslategray: 0x778899ff,
lightslategrey: 0x778899ff,
lightsteelblue: 0xb0c4deff,
lightyellow: 0xffffe0ff,
lime: 0x00ff00ff,
limegreen: 0x32cd32ff,
linen: 0xfaf0e6ff,
magenta: 0xff00ffff,
maroon: 0x800000ff,
mediumaquamarine: 0x66cdaaff,
mediumblue: 0x0000cdff,
mediumorchid: 0xba55d3ff,
mediumpurple: 0x9370dbff,
mediumseagreen: 0x3cb371ff,
mediumslateblue: 0x7b68eeff,
mediumspringgreen: 0x00fa9aff,
mediumturquoise: 0x48d1ccff,
mediumvioletred: 0xc71585ff,
midnightblue: 0x191970ff,
mintcream: 0xf5fffaff,
mistyrose: 0xffe4e1ff,
moccasin: 0xffe4b5ff,
navajowhite: 0xffdeadff,
navy: 0x000080ff,
oldlace: 0xfdf5e6ff,
olive: 0x808000ff,
olivedrab: 0x6b8e23ff,
orange: 0xffa500ff,
orangered: 0xff4500ff,
orchid: 0xda70d6ff,
palegoldenrod: 0xeee8aaff,
palegreen: 0x98fb98ff,
paleturquoise: 0xafeeeeff,
palevioletred: 0xdb7093ff,
papayawhip: 0xffefd5ff,
peachpuff: 0xffdab9ff,
peru: 0xcd853fff,
pink: 0xffc0cbff,
plum: 0xdda0ddff,
powderblue: 0xb0e0e6ff,
purple: 0x800080ff,
rebeccapurple: 0x663399ff,
red: 0xff0000ff,
rosybrown: 0xbc8f8fff,
royalblue: 0x4169e1ff,
saddlebrown: 0x8b4513ff,
salmon: 0xfa8072ff,
sandybrown: 0xf4a460ff,
seagreen: 0x2e8b57ff,
seashell: 0xfff5eeff,
sienna: 0xa0522dff,
silver: 0xc0c0c0ff,
skyblue: 0x87ceebff,
slateblue: 0x6a5acdff,
slategray: 0x708090ff,
slategrey: 0x708090ff,
snow: 0xfffafaff,
springgreen: 0x00ff7fff,
steelblue: 0x4682b4ff,
tan: 0xd2b48cff,
teal: 0x008080ff,
thistle: 0xd8bfd8ff,
tomato: 0xff6347ff,
turquoise: 0x40e0d0ff,
violet: 0xee82eeff,
wheat: 0xf5deb3ff,
white: 0xffffffff,
whitesmoke: 0xf5f5f5ff,
yellow: 0xffff00ff,
yellowgreen: 0x9acd32ff,
};
export const NAMED_COLORS = {
transparent: 0x00000000,
aliceblue: 0xf0f8ffff,
antiquewhite: 0xfaebd7ff,
aqua: 0x00ffffff,
aquamarine: 0x7fffd4ff,
azure: 0xf0ffffff,
beige: 0xf5f5dcff,
bisque: 0xffe4c4ff,
black: 0x000000ff,
blanchedalmond: 0xffebcdff,
blue: 0x0000ffff,
blueviolet: 0x8a2be2ff,
brown: 0xa52a2aff,
burlywood: 0xdeb887ff,
cadetblue: 0x5f9ea0ff,
chartreuse: 0x7fff00ff,
chocolate: 0xd2691eff,
coral: 0xff7f50ff,
cornflowerblue: 0x6495edff,
cornsilk: 0xfff8dcff,
crimson: 0xdc143cff,
cyan: 0x00ffffff,
darkblue: 0x00008bff,
darkcyan: 0x008b8bff,
darkgoldenrod: 0xb8860bff,
darkgray: 0xa9a9a9ff,
darkgreen: 0x006400ff,
darkgrey: 0xa9a9a9ff,
darkkhaki: 0xbdb76bff,
darkmagenta: 0x8b008bff,
darkolivegreen: 0x556b2fff,
darkorange: 0xff8c00ff,
darkorchid: 0x9932ccff,
darkred: 0x8b0000ff,
darksalmon: 0xe9967aff,
darkseagreen: 0x8fbc8fff,
darkslateblue: 0x483d8bff,
darkslategray: 0x2f4f4fff,
darkslategrey: 0x2f4f4fff,
darkturquoise: 0x00ced1ff,
darkviolet: 0x9400d3ff,
deeppink: 0xff1493ff,
deepskyblue: 0x00bfffff,
dimgray: 0x696969ff,
dimgrey: 0x696969ff,
dodgerblue: 0x1e90ffff,
firebrick: 0xb22222ff,
floralwhite: 0xfffaf0ff,
forestgreen: 0x228b22ff,
fuchsia: 0xff00ffff,
gainsboro: 0xdcdcdcff,
ghostwhite: 0xf8f8ffff,
gold: 0xffd700ff,
goldenrod: 0xdaa520ff,
gray: 0x808080ff,
green: 0x008000ff,
greenyellow: 0xadff2fff,
grey: 0x808080ff,
honeydew: 0xf0fff0ff,
hotpink: 0xff69b4ff,
indianred: 0xcd5c5cff,
indigo: 0x4b0082ff,
ivory: 0xfffff0ff,
khaki: 0xf0e68cff,
lavender: 0xe6e6faff,
lavenderblush: 0xfff0f5ff,
lawngreen: 0x7cfc00ff,
lemonchiffon: 0xfffacdff,
lightblue: 0xadd8e6ff,
lightcoral: 0xf08080ff,
lightcyan: 0xe0ffffff,
lightgoldenrodyellow: 0xfafad2ff,
lightgray: 0xd3d3d3ff,
lightgreen: 0x90ee90ff,
lightgrey: 0xd3d3d3ff,
lightpink: 0xffb6c1ff,
lightsalmon: 0xffa07aff,
lightseagreen: 0x20b2aaff,
lightskyblue: 0x87cefaff,
lightslategray: 0x778899ff,
lightslategrey: 0x778899ff,
lightsteelblue: 0xb0c4deff,
lightyellow: 0xffffe0ff,
lime: 0x00ff00ff,
limegreen: 0x32cd32ff,
linen: 0xfaf0e6ff,
magenta: 0xff00ffff,
maroon: 0x800000ff,
mediumaquamarine: 0x66cdaaff,
mediumblue: 0x0000cdff,
mediumorchid: 0xba55d3ff,
mediumpurple: 0x9370dbff,
mediumseagreen: 0x3cb371ff,
mediumslateblue: 0x7b68eeff,
mediumspringgreen: 0x00fa9aff,
mediumturquoise: 0x48d1ccff,
mediumvioletred: 0xc71585ff,
midnightblue: 0x191970ff,
mintcream: 0xf5fffaff,
mistyrose: 0xffe4e1ff,
moccasin: 0xffe4b5ff,
navajowhite: 0xffdeadff,
navy: 0x000080ff,
oldlace: 0xfdf5e6ff,
olive: 0x808000ff,
olivedrab: 0x6b8e23ff,
orange: 0xffa500ff,
orangered: 0xff4500ff,
orchid: 0xda70d6ff,
palegoldenrod: 0xeee8aaff,
palegreen: 0x98fb98ff,
paleturquoise: 0xafeeeeff,
palevioletred: 0xdb7093ff,
papayawhip: 0xffefd5ff,
peachpuff: 0xffdab9ff,
peru: 0xcd853fff,
pink: 0xffc0cbff,
plum: 0xdda0ddff,
powderblue: 0xb0e0e6ff,
purple: 0x800080ff,
rebeccapurple: 0x663399ff,
red: 0xff0000ff,
rosybrown: 0xbc8f8fff,
royalblue: 0x4169e1ff,
saddlebrown: 0x8b4513ff,
salmon: 0xfa8072ff,
sandybrown: 0xf4a460ff,
seagreen: 0x2e8b57ff,
seashell: 0xfff5eeff,
sienna: 0xa0522dff,
silver: 0xc0c0c0ff,
skyblue: 0x87ceebff,
slateblue: 0x6a5acdff,
slategray: 0x708090ff,
slategrey: 0x708090ff,
snow: 0xfffafaff,
springgreen: 0x00ff7fff,
steelblue: 0x4682b4ff,
tan: 0xd2b48cff,
teal: 0x008080ff,
thistle: 0xd8bfd8ff,
tomato: 0xff6347ff,
turquoise: 0x40e0d0ff,
violet: 0xee82eeff,
wheat: 0xf5deb3ff,
white: 0xffffffff,
whitesmoke: 0xf5f5f5ff,
yellow: 0xffff00ff,
yellowgreen: 0x9acd32ff
}

module.exports = NAMED_COLORS;
115 changes: 115 additions & 0 deletions src/point.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Represents a set of co-ordinates on a 2D plane
*
* @class Point
*/
export class Point {
/**
* Creates an instance of Point.
* @param {number} x X position
* @param {number} y Y position
*
* @memberof Point
*/
constructor (x, y) {
/**
* @type {number}
*/
this.x = x;

/**
* @type {number}
*/
this.y = y;
}
clone() {
return new Point(this.x,this.y)
}
distance(pt) {
return Math.sqrt(
Math.pow(pt.x-this.x,2)+
Math.pow(pt.y-this.y,2)
)
}
add(pt) {
return new Point(this.x+pt.x, this.y+pt.y)
}
subtract(pt) {
return new Point(this.x-pt.x, this.y-pt.y)
}
magnitude() {
return Math.sqrt(this.dotProduct(this))
}
dotProduct(v) {
return this.x*v.x + this.y*v.y
}
divide(scalar) {
return new Point(this.x/scalar, this.y/scalar)
}
floor() {
return new Point(Math.floor(this.x), Math.floor(this.y))
}
round() {
return new Point(Math.round(this.x), Math.round(this.y))
}
unit() {
return this.divide(this.magnitude())
}
rotate(theta) {
return new Point(
Math.cos(theta)*this.x - Math.sin(theta)*this.y,
Math.sin(theta)*this.x + Math.cos(theta)*this.y
)
}
scale(scalar) {
return new Point(
this.x*scalar,
this.y*scalar
)
}
equals(pt) {
return this.x === pt.x && this.y === pt.y;
}
}

export const toRad = (deg) => Math.PI/180*deg
export const toDeg = (rad) => 180/Math.PI*rad


export function calc_min_bounds(pts) {
let x1 = Number.POSITIVE_INFINITY
let y1 = Number.POSITIVE_INFINITY
let x2 = Number.NEGATIVE_INFINITY
let y2 = Number.NEGATIVE_INFINITY
pts.forEach(pt => {
x1 = Math.min(x1,pt.x)
y1 = Math.min(y1,pt.y)
x2 = Math.max(x2,pt.x)
y2 = Math.max(y2,pt.y)
})
return new Bounds(x1,y1,x2,y2)
}

export class Bounds {
constructor(x1,y1,x2,y2) {
this.x1 = x1
this.y1 = y1
this.x2 = x2
this.y2 = y2
}
contains(pt) {
if(pt.x < this.x1) return false
if(pt.x >= this.x2) return false
if(pt.y < this.y1) return false
if(pt.y >= this.y2) return false
return true
}

intersect(bds) {
let x1 = Math.max(this.x1,bds.x1)
let y1 = Math.max(this.y1,bds.y1)
let x2 = Math.min(this.x2,bds.x2)
let y2 = Math.min(this.y2,bds.y2)
return new Bounds(x1,y1,x2,y2)
}
}
42 changes: 24 additions & 18 deletions src/text.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const opentype = require('opentype.js');
import * as opentype from "opentype.js"


/**
* @type {object} Map containing all the fonts available for use
*/
var _fonts = { };
const _fonts = {}

/**
* The default font family to use for text
@@ -23,7 +23,7 @@ const DEFAULT_FONT_FAMILY = 'source';
*
* @returns {font} Font instance
*/
exports.registerFont = function(binaryPath, family, weight, style, variant) {
export function registerFont(binaryPath, family, weight, style, variant) {
_fonts[family] = {
binary: binaryPath,
family: family,
@@ -37,7 +37,7 @@ exports.registerFont = function(binaryPath, family, weight, style, variant) {
if(cb)cb();
return;
}
var self = this;
const self = this
opentype.load(binaryPath, function (err, font) {
if (err) throw new Error('Could not load font: ' + err);
self.loaded = true;
@@ -56,12 +56,17 @@ exports.registerFont = function(binaryPath, family, weight, style, variant) {
} catch (err) {
throw new Error('Could not load font: ' + err);
}
},
loadPromise: function() {
return new Promise((res,rej)=>{
this.load(()=>res())
})
}
};
return _fonts[family];
};
}
/**@ignore */
exports.debug_list_of_fonts = _fonts;
export const debug_list_of_fonts = _fonts;

/**
* Find Font
@@ -89,12 +94,12 @@ function findFont(family) {
*
* @returns {void}
*/
exports.processTextPath = function(ctx,text,x,y, fill, hAlign, vAlign) {
export function processTextPath(ctx,text,x,y, fill, hAlign, vAlign) {
let font = findFont(ctx._font.family);
if(!font) {
console.warn("Font missing",ctx._font)
}
const metrics = exports.measureText(ctx,text)
const metrics = measureText(ctx,text)
if(hAlign === 'start' || hAlign === 'left') /* x = x*/ ;
if(hAlign === 'end' || hAlign === 'right') x = x - metrics.width
if(hAlign === 'center') x = x - metrics.width/2
@@ -103,9 +108,9 @@ exports.processTextPath = function(ctx,text,x,y, fill, hAlign, vAlign) {
if(vAlign === 'top') y = y + metrics.emHeightAscent
if(vAlign === 'middle') y = y + metrics.emHeightAscent/2+metrics.emHeightDescent/2
if(vAlign === 'bottom') y = y + metrics.emHeightDescent
var size = ctx._font.size;
const size = ctx._font.size
font.load(function(){
var path = font.font.getPath(text, x, y, size);
const path = font.font.getPath(text, x, y, size)
ctx.beginPath();
path.commands.forEach(function(cmd) {
switch(cmd.type) {
@@ -122,7 +127,7 @@ exports.processTextPath = function(ctx,text,x,y, fill, hAlign, vAlign) {
}
});
});
};
}

/**
* Process Text Path
@@ -132,17 +137,18 @@ exports.processTextPath = function(ctx,text,x,y, fill, hAlign, vAlign) {
*
* @returns {object}
*/
exports.measureText = function(ctx,text) {
export function measureText(ctx,text) {
let font = findFont(ctx._font.family);
if(!font) console.warn("WARNING. Can't find font family ", ctx._font);
var fsize = ctx._font.size;
var glyphs = font.font.stringToGlyphs(text);
var advance = 0;
glyphs.forEach(function(g) { advance += g.advanceWidth; });
if(!font.font) console.warn("WARNING. Can't find font family ", ctx._font);
const fsize = ctx._font.size
const glyphs = font.font.stringToGlyphs(text)
let advance = 0
glyphs.forEach(function(g) { advance += g.advanceWidth; })

return {
width: advance/font.font.unitsPerEm*fsize,
emHeightAscent: font.font.ascender/font.font.unitsPerEm*fsize,
emHeightDescent: font.font.descender/font.font.unitsPerEm*fsize,
};
};
}
}
67 changes: 37 additions & 30 deletions src/transform.js
Original file line number Diff line number Diff line change
@@ -9,10 +9,12 @@
*/

"use strict";
import {Point} from './point.js'

/**
* @ignore
*/
function Transform(context) {
export function Transform(context) {
this.context = context;
this.matrix = [1,0,0,1,0,0]; //initialize with the identity matrix
this.stack = [];
@@ -38,20 +40,26 @@ function Transform(context) {
return [m[0],m[1],m[2],m[3],m[4],m[5]];
};

this.cloneTransform = function() {
let trans = new Transform()
trans.setMatrix(this.getMatrix())
return trans
}

//==========================================
// Stack
//==========================================

this.save = function() {
var matrix = this.cloneMatrix(this.getMatrix());
let matrix = this.cloneMatrix(this.getMatrix());
this.stack.push(matrix);

if (this.context) this.context.save();
};

this.restore = function() {
if (this.stack.length > 0) {
var matrix = this.stack.pop();
let matrix = this.stack.pop();
this.setMatrix(matrix);
}

@@ -83,12 +91,12 @@ function Transform(context) {
};

this.rotate = function(rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m11 = this.matrix[0] * c + this.matrix[2] * s;
var m12 = this.matrix[1] * c + this.matrix[3] * s;
var m21 = this.matrix[0] * -s + this.matrix[2] * c;
var m22 = this.matrix[1] * -s + this.matrix[3] * c;
const c = Math.cos(rad)
const s = Math.sin(rad)
const m11 = this.matrix[0] * c + this.matrix[2] * s
const m12 = this.matrix[1] * c + this.matrix[3] * s
const m21 = this.matrix[0] * -s + this.matrix[2] * c
const m22 = this.matrix[1] * -s + this.matrix[3] * c
this.matrix[0] = m11;
this.matrix[1] = m12;
this.matrix[2] = m21;
@@ -111,7 +119,7 @@ function Transform(context) {
//==========================================

this.rotateDegrees = function(deg) {
var rad = deg * Math.PI / 180;
const rad = deg * Math.PI / 180
this.rotate(rad);
};

@@ -135,14 +143,14 @@ function Transform(context) {
};

this.multiply = function(matrix) {
var m11 = this.matrix[0] * matrix.m[0] + this.matrix[2] * matrix.m[1];
var m12 = this.matrix[1] * matrix.m[0] + this.matrix[3] * matrix.m[1];
const m11 = this.matrix[0] * matrix.m[0] + this.matrix[2] * matrix.m[1]
const m12 = this.matrix[1] * matrix.m[0] + this.matrix[3] * matrix.m[1]

var m21 = this.matrix[0] * matrix.m[2] + this.matrix[2] * matrix.m[3];
var m22 = this.matrix[1] * matrix.m[2] + this.matrix[3] * matrix.m[3];
const m21 = this.matrix[0] * matrix.m[2] + this.matrix[2] * matrix.m[3]
const m22 = this.matrix[1] * matrix.m[2] + this.matrix[3] * matrix.m[3]

var dx = this.matrix[0] * matrix.m[4] + this.matrix[2] * matrix.m[5] + this.matrix[4];
var dy = this.matrix[1] * matrix.m[4] + this.matrix[3] * matrix.m[5] + this.matrix[5];
const dx = this.matrix[0] * matrix.m[4] + this.matrix[2] * matrix.m[5] + this.matrix[4]
const dy = this.matrix[1] * matrix.m[4] + this.matrix[3] * matrix.m[5] + this.matrix[5]

this.matrix[0] = m11;
this.matrix[1] = m12;
@@ -154,13 +162,13 @@ function Transform(context) {
};

this.invert = function() {
var d = 1 / (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2]);
var m0 = this.matrix[3] * d;
var m1 = -this.matrix[1] * d;
var m2 = -this.matrix[2] * d;
var m3 = this.matrix[0] * d;
var m4 = d * (this.matrix[2] * this.matrix[5] - this.matrix[3] * this.matrix[4]);
var m5 = d * (this.matrix[1] * this.matrix[4] - this.matrix[0] * this.matrix[5]);
const d = 1 / (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2])
const m0 = this.matrix[3] * d
const m1 = -this.matrix[1] * d
const m2 = -this.matrix[2] * d
const m3 = this.matrix[0] * d
const m4 = d * (this.matrix[2] * this.matrix[5] - this.matrix[3] * this.matrix[4])
const m5 = d * (this.matrix[1] * this.matrix[4] - this.matrix[0] * this.matrix[5])
this.matrix[0] = m0;
this.matrix[1] = m1;
this.matrix[2] = m2;
@@ -175,13 +183,12 @@ function Transform(context) {
//==========================================

this.transformPoint = function(pt) {
var x = pt.x;
var y = pt.y;
return {
x: x * this.matrix[0] + y * this.matrix[2] + this.matrix[4],
y: x * this.matrix[1] + y * this.matrix[3] + this.matrix[5]
};
const x = pt.x
const y = pt.y
return new Point(
x * this.matrix[0] + y * this.matrix[2] + this.matrix[4],
x * this.matrix[1] + y * this.matrix[3] + this.matrix[5],
)
};
}

exports.Transform = Transform;
130 changes: 49 additions & 81 deletions src/uint32.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@

//from https://github.com/fxa/uint32.js
'use strict';

const POW_2_32 = 0x0100000000
const POW_2_52 = 0x10000000000000

/* jshint bitwise: false */
// Creating and Extracting
//

/**
* @license (c) Franz X Antesberger 2013
* Creates an uint32 from the given bytes in big endian order.
* @param {Number} highByte the high byte
* @param {Number} secondHighByte the 2nd high byte
* @param {Number} thirdHighByte the 3rd high byte
* @param {Number} lowByte the low byte
* @returns highByte concat secondHighByte concat thirdHighByte concat lowByte
*/
(function (exporter) {
'use strict';

var POW_2_32 = 0x0100000000;
var POW_2_52 = 0x10000000000000;

//
// Creating and Extracting
//

/**
* Creates an uint32 from the given bytes in big endian order.
* @param {Number} highByte the high byte
* @param {Number} secondHighByte the 2nd high byte
* @param {Number} thirdHighByte the 3rd high byte
* @param {Number} lowByte the low byte
* @returns highByte concat secondHighByte concat thirdHighByte concat lowByte
*/
exporter.fromBytesBigEndian = function (highByte, secondHighByte, thirdHighByte, lowByte) {
return ((highByte << 24) | (secondHighByte << 16) | (thirdHighByte << 8) | lowByte) >>> 0;
};
export const fromBytesBigEndian = function (highByte, secondHighByte, thirdHighByte, lowByte) {
return ((highByte << 24) | (secondHighByte << 16) | (thirdHighByte << 8) | lowByte) >>> 0;
};

/**
* Returns the byte.
@@ -35,7 +26,7 @@
* @param {Number} byteNo 0-3 the byte number, 0 is the high byte, 3 the low byte
* @returns {Number} the 0-255 byte according byteNo
*/
exporter.getByteBigEndian = function (uint32value, byteNo) {
export const getByteBigEndian = function (uint32value, byteNo) {
return (uint32value >>> (8 * (3 - byteNo))) & 0xff;
};

@@ -44,12 +35,12 @@
* @param {Number} uint32value the source to be extracted
* @returns {Array} the array [highByte, 2ndHighByte, 3rdHighByte, lowByte]
*/
exporter.getBytesBigEndian = function (uint32value) {
export const getBytesBigEndian = function (uint32value) {
return [
exporter.getByteBigEndian(uint32value, 0),
exporter.getByteBigEndian(uint32value, 1),
exporter.getByteBigEndian(uint32value, 2),
exporter.getByteBigEndian(uint32value, 3)
getByteBigEndian(uint32value, 0),
getByteBigEndian(uint32value, 1),
getByteBigEndian(uint32value, 2),
getByteBigEndian(uint32value, 3)
];
};

@@ -58,9 +49,9 @@
* @param {Number} uint32value the uint32 to be stringified
* @param {Number} optionalMinLength the optional (default 8)
*/
exporter.toHex = function (uint32value, optionalMinLength) {
export const toHex = function (uint32value, optionalMinLength) {
optionalMinLength = optionalMinLength || 8;
var result = uint32value.toString(16);
let result = uint32value.toString(16)
if (result.length < optionalMinLength) {
result = new Array(optionalMinLength - result.length + 1).join('0') + result;
}
@@ -72,7 +63,7 @@
* @param {Number} number the number to be converted.
* @return {Number} an uint32 value
*/
exporter.toUint32 = function (number) {
export const toUint32 = function (number) {
// the shift operator forces js to perform the internal ToUint32 (see ecmascript spec 9.6)
return number >>> 0;
};
@@ -83,7 +74,7 @@
* @param {Number} number the number to extract the high part
* @return {Number} the high part of the number
*/
exporter.highPart = function (number) {
export const highPart = function (number) {
return exporter.toUint32(number / POW_2_32);
};

@@ -97,9 +88,9 @@
* @param {Number} argv one or more uint32 values
* @return {Number} the bitwise OR uint32 value
*/
exporter.or = function (uint32val0, argv) {
var result = uint32val0;
for (var index = 1; index < arguments.length; index += 1) {
export const or = function (uint32val0, argv) {
let result = uint32val0
for (let index = 1; index < arguments.length; index += 1) {
result = (result | arguments[index]);
}
return result >>> 0;
@@ -111,9 +102,9 @@
* @param {Number} argv one or more uint32 values
* @return {Number} the bitwise AND uint32 value
*/
exporter.and = function (uint32val0, argv) {
var result = uint32val0;
for (var index = 1; index < arguments.length; index += 1) {
export const and = function (uint32val0, argv) {
let result = uint32val0
for (let index = 1; index < arguments.length; index += 1) {
result = (result & arguments[index]);
}
return result >>> 0;
@@ -125,15 +116,15 @@
* @param {Number} argv one or more uint32 values
* @return {Number} the bitwise XOR uint32 value
*/
exporter.xor = function (uint32val0, argv) {
var result = uint32val0;
for (var index = 1; index < arguments.length; index += 1) {
export const xor = function (uint32val0, argv) {
let result = uint32val0
for (let index = 1; index < arguments.length; index += 1) {
result = (result ^ arguments[index]);
}
return result >>> 0;
};

exporter.not = function (uint32val) {
export const not = function (uint32val) {
return (~uint32val) >>> 0;
};

@@ -147,7 +138,7 @@
* @param {Number} numBits the number of bits to be shifted (0-31)
* @returns {Number} the uint32 value of the shifted word
*/
exporter.shiftLeft = function (uint32val, numBits) {
export const shiftLeft = function (uint32val, numBits) {
return (uint32val << numBits) >>> 0;
};

@@ -157,15 +148,15 @@
* @param {Number} numBits the number of bits to be shifted (0-31)
* @returns {Number} the uint32 value of the shifted word
*/
exporter.shiftRight = function (uint32val, numBits) {
export const shiftRight = function (uint32val, numBits) {
return uint32val >>> numBits;
};

exporter.rotateLeft = function (uint32val, numBits) {
export const rotateLeft = function (uint32val, numBits) {
return (((uint32val << numBits) >>> 0) | (uint32val >>> (32 - numBits))) >>> 0;
};

exporter.rotateRight = function (uint32val, numBits) {
export const rotateRight = function (uint32val, numBits) {
return (((uint32val) >>> (numBits)) | ((uint32val) << (32 - numBits)) >>> 0) >>> 0;
};

@@ -176,15 +167,15 @@
/**
* Bitwise choose bits from y or z, as a bitwise x ? y : z
*/
exporter.choose = function (x, y, z) {
export const choose = function (x, y, z) {
return ((x & (y ^ z)) ^ z) >>> 0;
};

/**
* Majority gate for three parameters. Takes bitwise the majority of x, y and z,
* @see https://en.wikipedia.org/wiki/Majority_function
*/
exporter.majority = function (x, y, z) {
export const majority = function (x, y, z) {
return ((x & (y | z)) | (y & z)) >>> 0;
};

@@ -196,9 +187,9 @@
* Adds the given values modulus 2^32.
* @returns the sum of the given values modulus 2^32
*/
exporter.addMod32 = function (uint32val0/*, optionalValues*/) {
var result = uint32val0;
for (var index = 1; index < arguments.length; index += 1) {
export const addMod32 = function (uint32val0/*, optionalValues*/) {
let result = uint32val0
for (let index = 1; index < arguments.length; index += 1) {
result += arguments[index];
}
return result >>> 0;
@@ -209,45 +200,22 @@
* @param {Number} uint32val the value, the log2 is calculated of
* @return {Number} the logarithm base 2, an integer between 0 and 31
*/
exporter.log2 = function (uint32val) {
export const log2 = function (uint32val) {
return Math.floor(Math.log(uint32val) / Math.LN2);
};

/*
// this implementation does the same, looks much funnier, but takes 2 times longer (according to jsperf) ...
var log2_u = new Uint32Array(2);
var log2_d = new Float64Array(log2_u.buffer);
exporter.log2 = function (uint32val) {
// Ported from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float to javascript
// (public domain)
if (uint32val === 0) {
return -Infinity;
}
// fill in the low part
log2_u[0] = uint32val;
// set the mantissa to 2^52
log2_u[1] = 0x43300000;
// subtract 2^52
log2_d[0] -= 0x10000000000000;
return (log2_u[1] >>> 20) - 0x3FF;
};
*/

/**
* Returns the the low and the high uint32 of the multiplication.
* @param {Number} factor1 an uint32
* @param {Number} factor2 an uint32
* @param {Uint32Array[2]} resultUint32Array2 the Array, where the result will be written to
* @returns undefined
*/
exporter.mult = function (factor1, factor2, resultUint32Array2) {
var high16 = ((factor1 & 0xffff0000) >>> 0) * factor2;
var low16 = (factor1 & 0x0000ffff) * factor2;
export const mult = function (factor1, factor2, resultUint32Array2) {
const high16 = ((factor1 & 0xffff0000) >>> 0) * factor2
const low16 = (factor1 & 0x0000ffff) * factor2
// the addition is dangerous, because the result will be rounded, so the result depends on the lowest bits, which will be cut away!
var carry = ((exporter.toUint32(high16) + exporter.toUint32(low16)) >= POW_2_32) ? 1 : 0;
const carry = ((exporter.toUint32(high16) + exporter.toUint32(low16)) >= POW_2_32) ? 1 : 0
resultUint32Array2[0] = (exporter.highPart(high16) + exporter.highPart(low16) + carry) >>> 0;
resultUint32Array2[1] = ((high16 >>> 0) + (low16 >>> 0));// >>> 0;
};

}) ((typeof module !== 'undefined') ? module.exports = {} : window.uint32 = {});
10 changes: 6 additions & 4 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const NAMED_COLORS = require('./named_colors');
import {NAMED_COLORS} from "./named_colors.js"

/**
* Clamping is the process of limiting a position to an area
@@ -11,7 +11,7 @@ const NAMED_COLORS = require('./named_colors');
*
* @returns {number}
*/
exports.clamp = function (value,min,max) {
export function clamp(value,min,max) {
if(value < min) return min;
if(value > max) return max;
return value;
@@ -34,10 +34,10 @@ exports.clamp = function (value,min,max) {
*
* @returns {number}
*/
exports.lerp = function(a,b,t) { return a + (b-a)*t; }
export const lerp = function(a,b,t) { return a + (b-a)*t; }


exports.colorStringToUint32 = function(str) {
export const colorStringToUint32 = function(str) {
if(!str) return 0x000000;
//hex values always get 255 for the alpha channel
if(str.indexOf('#')===0) {
@@ -63,3 +63,5 @@ exports.colorStringToUint32 = function(str) {
}
throw new Error("unknown style format: " + str );
}


38 changes: 38 additions & 0 deletions test/bugs/bug_129.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<canvas id="canvas" width="512" height="512"></canvas>
<script type="module">

let canvas = document.querySelector('#canvas')
let ctx = canvas.getContext('2d')

let points = [{
p:[

246, 338, 246, 382,
246, 414, 300, 414,
366, 414, 366, 371,
366, 338, 327, 338
]
}
];
ctx.strokeColor = '#000';
let i = 0
ctx.beginPath();
ctx.moveTo(points[i].p[14], points[i].p[15]); //each bezierCurve strangely centers back to the start point moveTo!
ctx.bezierCurveTo(points[i].p[14], points[i].p[15],points[i].p[0], points[i].p[1], points[i].p[2], points[i].p[3]);
ctx.bezierCurveTo(points[i].p[2], points[i].p[3],points[i].p[4], points[i].p[5], points[i].p[6], points[i].p[7]);
// ctx.bezierCurveTo(points[i].p[6], points[i].p[7],points[i].p[8], points[i].p[9], points[i].p[10], points[i].p[11]);
// ctx.bezierCurveTo(points[i].p[10], points[i].p[11],points[i].p[12], points[i].p[13], points[i].p[14], points[i].p[15]);
ctx.stroke();
// if one uses ctx.fill() instead this will be an oval though
</script>

</body>
</html>
36 changes: 36 additions & 0 deletions test/bugs/bug_129.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as pureimage from '../../src/index.js'
import fs from 'fs'
import path from 'path'
const DIR = "output"

let img = pureimage.make(512, 512);
let ctx = img.getContext('2d');

ctx.fillStyle = 'white'
ctx.fillRect(0,0,img.width,img.height)

let points = [{
p:[

246, 338, 246, 382,
246, 414, 300, 414,
366, 414, 366, 371,
366, 338, 327, 338
]
}
];
// ctx.strokeColor = '#000';
ctx.strokeStyle = '#000000'
let i = 0
ctx.beginPath();
console.log(points[i].p[14])
ctx.moveTo(points[i].p[14], points[i].p[15]); //each bezierCurve strangely centers back to the start point moveTo!
ctx.bezierCurveTo(points[i].p[14], points[i].p[15],points[i].p[0], points[i].p[1], points[i].p[2], points[i].p[3]);
ctx.bezierCurveTo(points[i].p[2], points[i].p[3],points[i].p[4], points[i].p[5], points[i].p[6], points[i].p[7]);
// ctx.bezierCurveTo(points[i].p[6], points[i].p[7],points[i].p[8], points[i].p[9], points[i].p[10], points[i].p[11]);
// ctx.bezierCurveTo(points[i].p[10], points[i].p[11],points[i].p[12], points[i].p[13], points[i].p[14], points[i].p[15]);
ctx.stroke();

pureimage.encodePNGToStream(img, fs.createWriteStream(path.join(DIR,'bug_129.png'))).then(()=>{
console.log("rendred bug 129")
})
62 changes: 62 additions & 0 deletions test/clipping.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import chai, {expect} from "chai"

import * as pureimage from "../src/index.js"
import fs from 'fs'
import path from 'path'
import {write_png} from './common.js'

describe('clipping tests',() => {
let image
let context

beforeEach(() => {
image = pureimage.make(200, 200)
context = image.getContext('2d')
})

it('canvas is empty and clear', (done) => {
expect(image.getPixelRGBA(0, 0)).to.eq(0x00000000)
done()
})

it('can fill with white and red', (done) => {
context.fillStyle = 'white';
context.fillRect(0, 0, 200, 200);
context.beginPath();
context.arc(100,100, 50, 0, Math.PI*2,false)
context.clip();
context.fillStyle = 'red';
context.fillRect(0, 0, 200, 200);
write_png(image,'clipcolor').then(()=>{
console.log("wrote out clipcolor.png")
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(100,100)).to.eq(0xFF0000FF)
done()
})
})

it('can draw an image inside of a clip',(done)=>{
context.fillStyle = 'red';
context.fillRect(0, 0, 200, 200);
context.beginPath();
context.arc(100,100, 10, 0, Math.PI*2,false)
context.clip();
context.fillStyle = 'white';
context.fillRect(0, 0, 200, 200);
let src = pureimage.make(50,50)
const c = src.getContext('2d')
c.fillStyle = 'white'
c.fillRect(0,0,50,50)
c.fillStyle = 'black'
c.fillRect(25,0,25,50)
context.drawImage(src,75,75,50,50)
write_png(image,'clipimage').then(()=>{
expect(image.getPixelRGBA(0, 0)).to.eq(0xFF0000FF)
expect(image.getPixelRGBA(99, 100)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(80, 100)).to.eq(0xFF0000FF)
done()
})
})


})
11 changes: 7 additions & 4 deletions tests/unit/specs/color.test.js → test/color.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const pureimage = require('pureimage')
import chai, {expect} from "chai"

import * as pureimage from "../src/index.js"

describe('color',() => {
let image
let context
@@ -9,22 +12,22 @@ describe('color',() => {
})

it('canvas is empty and clear', (done) => {
expect(image.getPixelRGBA(0,0)).toBe(0x00000000)
expect(image.getPixelRGBA(0,0)).to.eq(0x00000000)
done()
})

it('fillcolor_hex', (done)=>{
context.fillStyle = '#000000'
context.fillRect(0,0,200,200)
expect(image.getPixelRGBA(0,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(0,0)).to.eq(0x000000FF)
done()
})

it('fillcolor_hex_3', (done)=>{
function fill_check(hex_string, num) {
context.fillStyle = hex_string
context.fillRect(0,0,200,200)
expect(image.getPixelRGBA(0,0)).toBe(num)
expect(image.getPixelRGBA(0,0)).to.eq(num)
}

fill_check('#000000',0x000000FF)
19 changes: 19 additions & 0 deletions test/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import fs from 'fs'
import * as pureimage from '../src/index.js'
import path from 'path'

export const FIXTURES_DIR = "test/unit/fixtures/"


export const mkdir = (pth) => {
return new Promise((res,rej)=>{
fs.mkdir(pth,(e)=>{
// console.log("done with mkdir",e)
res()
})
})
}

export function write_png (image,filename) {
return pureimage.encodePNGToStream(image, fs.createWriteStream(path.join('output',filename+".png")))
}
26 changes: 14 additions & 12 deletions tests/unit/specs/drawimage.test.js → test/drawimage.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const pureimage = require('pureimage')
import chai, {expect} from "chai"
import * as pureimage from "../src/index.js"

describe('drawImage',() => {
let image;
let context;
@@ -17,37 +19,37 @@ describe('drawImage',() => {
})

it('canvas is empty and clear', (done) => {
expect(image.getPixelRGBA(0,0)).toBe(0x00000000)
expect(image.getPixelRGBA(0,0)).to.eq(0x00000000)
done()
})

it('can draw a full image', (done) => {
context.drawImage(src,0,0,50,50,0,0,50,50)
expect(image.getPixelRGBA(0, 0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).to.eq(0x000000FF)
done()
})

it('can stretch a full image', (done) => {
context.drawImage(src,0,0,50,50,0,0,200,200)
expect(image.getPixelRGBA(0, 0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(100,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(199,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(100,0)).to.eq(0x000000FF)
expect(image.getPixelRGBA(199,0)).to.eq(0x000000FF)
done()
})

it('can draw a plain image',(done) => {
context.drawImage(src,0,0)
expect(image.getPixelRGBA(0, 0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).to.eq(0x000000FF)
done()
})

it('can draw a scaled image',(done) => {
context.drawImage(src,0,0,200,200)
expect(image.getPixelRGBA(0, 0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(100,0)).toBe(0x000000FF)
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(100,0)).to.eq(0x000000FF)
done()
})
})
12 changes: 12 additions & 0 deletions test/examples/make_image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as PImage from "../../src/index.js"
import * as fs from 'fs'
const img1 = PImage.make(100, 100)
const ctx = img1.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(0,0,100,100);

PImage.encodePNGToStream(img1, fs.createWriteStream('out.png')).then(() => {
console.log("wrote out the png file to out.png");
}).catch((e)=>{
console.log("there was an error writing");
});
32 changes: 32 additions & 0 deletions test/examples/stream_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
This examples streams an image from a URL to a PureImage buffer,
draws the current date in big black letters,
and writes the final image to disk
*/
import * as PImage from "../../src/index.js"
import fs from 'fs'
import * as client from "https"

let url = "https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png"
let filepath = "output.png"
//register font
const font = PImage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf','MyFont');
//load font
font.load(() => {
//get image
client.get(url, (image_stream)=>{
//decode image
PImage.decodePNGFromStream(image_stream).then(img => {
//get context
const ctx = img.getContext('2d');
ctx.fillStyle = '#000000';
ctx.font = "60pt MyFont";
ctx.fillText(new Date().toDateString(), 50, 80);
PImage.encodePNGToStream(img, fs.createWriteStream(filepath)).then(()=>{
console.log("done writing to ",filepath)
})
});
})
})

34 changes: 34 additions & 0 deletions test/examples/stream_promise_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
This examples streams an image from a URL to a PureImage buffer,
draws the current date in big black letters,
and writes the final image to disk
*/
import * as PImage from "../../src/index.js"
import fs from 'fs'
import * as client from "https"

let url = "https://vr.josh.earth/webxr-experiments/physics/jinglesmash.thumbnail.png"
let filepath = "output.png"
let fontpath = 'test/unit/fixtures/fonts/SourceSansPro-Regular.ttf'
PImage.registerFont(fontpath,'MyFont').loadPromise()
//Promise hack because https module doesn't support promises)
.then(()=>new Promise(res => client.get(url,res)))
.then(stream => PImage.decodePNGFromStream(stream))
.then(img => {
//get context
const ctx = img.getContext('2d');
ctx.fillStyle = '#000000';
ctx.font = "60pt MyFont";
ctx.fillText(new Date().toDateString(), 50, 80);
return PImage.encodePNGToStream(img, fs.createWriteStream(filepath))
})
.then(()=>{
console.log('done writing',filepath)
})






Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const pureimage = require('pureimage')
const fs = require('fs')
import chai, {expect} from "chai"
import * as pureimage from "../src/index.js"
import fs from "fs"

describe('drawing gradients',() => {

let image
@@ -24,8 +26,8 @@ describe('drawing gradients',() => {

pureimage.encodePNGToStream(image, fs.createWriteStream('lgrad.png')).then(() => {
console.log('wrote out lgrad.png')
expect(image.getPixelRGBA(0, 0)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(19, 19)).toBe(0x0C0CFFFF)
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(19, 19)).to.eq(0x0C0CFFFF)
done()
})
})
@@ -39,8 +41,11 @@ describe('drawing gradients',() => {

pureimage.encodePNGToStream(image, fs.createWriteStream('rgrad.png')).then(() => {
console.log('wrote out rgrad.png')
expect(image.getPixelRGBA(0, 0)).toBe(0x00FF00FF)
expect(image.getPixelRGBA(10, 10)).toBe(0xFFFFFFFF)
expect(image.getPixelRGBA(0, 0)).to.eq(0x00FF00FF)
expect(image.getPixelRGBA(10, 10)).to.eq(0xFFFFFFFF)
done()
}).catch(e => {
console.error(e)
done()
})
})
26 changes: 14 additions & 12 deletions tests/unit/specs/imagedata.test.js → test/imagedata.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const pureimage = require('pureimage')
import chai, {expect} from "chai"
import * as pureimage from "../src/index.js"

describe('drawImage',() => {
let image;
let context;
@@ -18,15 +20,15 @@ describe('drawImage',() => {
context.fillRect(0,0,200,200)
context.fillStyle = 'red'
context.fillRect(0,0,100,100)
expect(image.getPixelRGBA(5,5)).toBe(RED)
expect(image.getPixelRGBA(105,105)).toBe(WHITE)
expect(image.getPixelRGBA(5,5)).to.eq(RED)
expect(image.getPixelRGBA(105,105)).to.eq(WHITE)
done()
})

it('canvas can get image data', (done) => {
let id = context.getImageData(0,0,10,10)
expect(id.width).toBe(10)
expect(id.height).toBe(10)
expect(id.width).to.eq(10)
expect(id.height).to.eq(10)
done()
})

@@ -35,16 +37,16 @@ describe('drawImage',() => {
context.fillRect(0,0,200,200)
context.fillStyle = 'red'
context.fillRect(0,0,100,100)
expect(image.getPixelRGBA(99,99)).toBe(RED)
expect(image.getPixelRGBA(101,101)).toBe(WHITE)
expect(image.getPixelRGBA(99,99)).to.eq(RED)
expect(image.getPixelRGBA(101,101)).to.eq(WHITE)

let id = context.getImageData(95,95,10,10)
console.log(id.getPixelRGBA_separate(0,0))
console.log(id.getPixelRGBA_separate(1,0))
console.log(id.getPixelRGBA_separate(2,0))

expect(id.getPixelRGBA(3,3)).toBe(RED)
expect(id.getPixelRGBA(8,8)).toBe(WHITE)
expect(id.getPixelRGBA(3,3)).to.eq(RED)
expect(id.getPixelRGBA(8,8)).to.eq(WHITE)
done()
})

@@ -57,7 +59,7 @@ describe('drawImage',() => {
id.data[2] = 0
id.data[3] = 255
context.putImageData(id,0,0)
expect(image.getPixelRGBA(0,0)).toBe(GREEN)
expect(image.getPixelRGBA(0,0)).to.eq(GREEN)
done()
})

@@ -68,8 +70,8 @@ describe('drawImage',() => {
id.setPixelRGBA(0,0,RED)
id.setPixelRGBA(8,8,GREEN)
context.putImageData(id,100,100)
expect(image.getPixelRGBA(100,100)).toBe(RED)
expect(image.getPixelRGBA(108,108)).toBe(GREEN)
expect(image.getPixelRGBA(100,100)).to.eq(RED)
expect(image.getPixelRGBA(108,108)).to.eq(GREEN)
done()
})

22 changes: 12 additions & 10 deletions tests/unit/specs/line.test.js → test/line.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const Point = require('Point');
const Line = require('Line');
import chai, {expect} from "chai"

import {Line} from "../src/line.js"
import {Point} from "../src/point.js"

/**
* @test {Line}
@@ -13,15 +15,15 @@ describe('Line', () => {
const start = new Point(6, 8);
const end = new Point(12, 6);

expect(() => new Line(start, end)).not.toThrow(TypeError);
expect(() => new Line(start, end)).not.throw(TypeError);
});

/**
* @test {Line#constructor}
*/
it('can be created from 4 numbers and uses them as start and end points', () => {
expect(() => new Line(6, 8, 12, 6)).not.toThrow();
expect(() => new Line({}, "spaghetti", "hello world", [])).toThrow(TypeError);
expect(() => new Line(6, 8, 12, 6)).not.throw();
expect(() => new Line({}, "spaghetti", "hello world", [])).to.throw(TypeError);
});

/**
@@ -30,10 +32,10 @@ describe('Line', () => {
it('can only be created with either 2 or 4 arguments', () => {
const errorMsg = 'Please pass either two Point objects, or 4 integers to the constructor';

expect(() => new Line()).toThrow(errorMsg);
expect(() => new Line(12)).toThrow(errorMsg);
expect(() => new Line(12, 30, 92)).toThrow(errorMsg);
expect(() => new Line(12, 30, 92, 10, 99)).toThrow(errorMsg);
expect(() => new Line()).to.throw(errorMsg);
expect(() => new Line(12)).to.throw(errorMsg);
expect(() => new Line(12, 30, 92)).to.throw(errorMsg);
expect(() => new Line(12, 30, 92, 10, 99)).to.throw(errorMsg);
});

/**
@@ -45,6 +47,6 @@ describe('Line', () => {

const line = new Line(start, end);

expect(line.getLength()).toBe(10);
expect(line.getLength()).to.eq(10);
})
});
104 changes: 104 additions & 0 deletions test/mathtest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
export class Point {
/**
* Creates an instance of Point.
* @param {number} x X position
* @param {number} y Y position
*
* @memberof Point
*/
constructor (x, y) {
/**
* @type {number}
*/
this.x = x;

/**
* @type {number}
*/
this.y = y;
}

distance(pt) {
return Math.sqrt(
Math.pow(pt.x-this.x,2)+
Math.pow(pt.y-this.y,2)
)
}

unit(pt) {
return this.divide(this.magnitude())
}

subtract(pt) {
return new Point(this.x-pt.x, this.y-pt.y)
}
add(pt) {
return new Point(this.x+pt.x, this.y+pt.y)
}

magnitude() {
return Math.sqrt(this.dotProduct(this))
}

dotProduct(v) {
return this.x*v.x + this.y*v.y
}

divide(scalar) {
return new Point(this.x/scalar, this.y/scalar)
}

floor() {
return new Point(Math.floor(this.x), Math.floor(this.y))
}
round() {
return new Point(Math.round(this.x), Math.round(this.y))
}

rotate(theta) {
return new Point(
Math.cos(theta)*this.x - Math.sin(theta)*this.y,
Math.sin(theta)*this.x + Math.cos(theta)*this.y
)
}
scale(scalar) {
return new Point(
this.x*scalar,
this.y*scalar
)
}
}

const l = (...args) => console.log(...args)
const toRad = (deg) => Math.PI/180*deg

let AB = new Point(10,10)
l('ab',AB)
let ORIGIN = new Point(0,0)
l('origin',ORIGIN)
let uAB = ORIGIN.subtract(AB).unit()
l("uAB",uAB)
let uBC = uAB.rotate(toRad(90))
l("uBC",uBC)
let uBCs = uBC.scale(5)
l("scaled",uBCs)
let fuBCS = uBCs.add(AB)
l("final",fuBCS)

let fuBDS = uAB.rotate(toRad(-90)).scale(5).add(AB)
l('other final',fuBDS)


function project(A,B,scale) {
let delta_unit = A.subtract(B).unit()
let C_unit = delta_unit.rotate(toRad(90))
let D_unit = delta_unit.rotate(toRad(-90))
return [
C_unit.scale(scale).add(B),
D_unit.scale(scale).add(B)
]
}

l("test two",project(new Point(0,0),new Point(10,10),5))
l("test two",project(new Point(10,10),new Point(20,20),5))
l('test two',project(new Point(20,20),new Point(30,20),5))
File renamed without changes.
File renamed without changes.
92 changes: 70 additions & 22 deletions tests/unit/specs/path.test.js → test/path.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const pureimage = require('pureimage')
const fs = require('fs')
const path = require("path")
import chai, {expect} from "chai"
import * as pureimage from "../src/index.js"
import fs from "fs"
import path from "path"
const DIR = "output"
const mkdir = (pth) => {
return new Promise((res,rej)=>{
@@ -26,7 +27,7 @@ describe('draw curve',() => {
})

it('canvas is empty and clear', (done) => {
expect(image.getPixelRGBA(0,0)).toBe(WHITE)
expect(image.getPixelRGBA(0,0)).to.eq(WHITE)
done()
})

@@ -39,10 +40,10 @@ describe('draw curve',() => {
c.lineTo(10,10)
c.fillStyle = 'black'
c.fill()
expect(image.getPixelRGBA(0,0)).toBe(WHITE)
expect(image.getPixelRGBA(11,11)).toBe(BLACK)
expect(image.getPixelRGBA(50,50)).toBe(BLACK)
expect(image.getPixelRGBA(100,100)).toBe(WHITE)
expect(image.getPixelRGBA(0,0)).to.eq(WHITE)
expect(image.getPixelRGBA(11,11)).to.eq(BLACK)
expect(image.getPixelRGBA(50,50)).to.eq(BLACK)
expect(image.getPixelRGBA(100,100)).to.eq(WHITE)
done()
})

@@ -51,10 +52,10 @@ describe('draw curve',() => {
c.rect(10,10,90,90)
c.fillStyle = 'black'
c.fill()
expect(image.getPixelRGBA(0,0)).toBe(WHITE)
expect(image.getPixelRGBA(11,11)).toBe(BLACK)
expect(image.getPixelRGBA(50,50)).toBe(BLACK)
expect(image.getPixelRGBA(100,100)).toBe(WHITE)
expect(image.getPixelRGBA(0,0)).to.eq(WHITE)
expect(image.getPixelRGBA(11,11)).to.eq(BLACK)
expect(image.getPixelRGBA(50,50)).to.eq(BLACK)
expect(image.getPixelRGBA(100,100)).to.eq(WHITE)
done()
})

@@ -71,13 +72,13 @@ describe('draw curve',() => {
c.fill()
pureimage.encodePNGToStream(image, fs.createWriteStream(path.join(DIR,'bezier1.png'))).then(() => {
console.log('wrote out bezier1.png')
expect(image.getPixelRGBA(0, 0)).toBe(WHITE)
expect(image.getPixelRGBA(19, 39)).toBe(BLACK)
expect(image.getPixelRGBA(0, 0)).to.eq(WHITE)
expect(image.getPixelRGBA(19, 39)).to.eq(BLACK)
done()
})

// expect(image.getPixelRGBA(0,0)).toBe(WHITE)
// expect(image.getPixelRGBA(20,15)).toBe(BLACK)
// expect(image.getPixelRGBA(0,0)).to.eq(WHITE)
// expect(image.getPixelRGBA(20,15)).to.eq(BLACK)
// done()
})

@@ -132,8 +133,8 @@ describe('draw curve',() => {
ctx.fill();
pureimage.encodePNGToStream(img, fs.createWriteStream(path.join(DIR,'northgoing.png'))).then(()=>{
console.log('wrote out northgoing.png')
expect(img.getPixelRGBA(25, 110)).toBe(BLACK)
expect(img.getPixelRGBA(25, 90)).toBe(BLACK)
expect(img.getPixelRGBA(25, 110)).to.eq(BLACK)
expect(img.getPixelRGBA(25, 90)).to.eq(BLACK)
done()
})

@@ -147,14 +148,61 @@ describe('draw curve',() => {
c.lineTo(10,10)
c.fillStyle = 'transparent'
c.fill()
expect(image.getPixelRGBA(0,0)).toBe(WHITE)
expect(image.getPixelRGBA(11,11)).toBe(WHITE)
expect(image.getPixelRGBA(50,50)).toBe(WHITE)
expect(image.getPixelRGBA(100,100)).toBe(WHITE)
expect(image.getPixelRGBA(0,0)).to.eq(WHITE)
expect(image.getPixelRGBA(11,11)).to.eq(WHITE)
expect(image.getPixelRGBA(50,50)).to.eq(WHITE)
expect(image.getPixelRGBA(100,100)).to.eq(WHITE)



done()
})
it("draws thick square",(done) => {
c.fillStyle = 'white'
c.fillRect(0,0,200,200)
c.beginPath()
//square
c.moveTo(10,10)
c.lineTo(60,10)
c.lineTo(60,60)
c.lineTo(120,60)
c.lineTo(120,10)
c.lineTo(180,10)
c.lineTo(180,100)
c.lineTo(10,100)
// c.lineTo(10,10)
c.strokeStyle = 'black'
c.lineWidth = 4
c.fillStyle = 'black'
c.stroke()
// c.fillStyle = 'black'
// c.fill()
pureimage.encodePNGToStream(image, fs.createWriteStream(path.join(DIR,'thick_stroke_square.png'))).then(() => {
console.log('wrote out thick_stroke.png')
// expect(image.getPixelRGBA(0, 0)).to.eq(WHITE)
// expect(image.getPixelRGBA(19, 39)).to.eq(BLACK)
done()
})
})
it("draws thick curve",(done) => {
c.fillStyle = 'white'
c.fillRect(0,0,200,200)

c.beginPath()
c.moveTo(10,10)
c.bezierCurveTo(50,50, 100,50, 10,100)
c.lineTo(10,10)
c.strokeStyle = 'black'
c.lineWidth = 2
c.fillStyle = 'black'
c.stroke()
// c.fillStyle = 'black'
// c.fill()
pureimage.encodePNGToStream(image, fs.createWriteStream(path.join(DIR,'thick_stroke_curve.png'))).then(() => {
console.log('wrote out thick_stroke.png')
// expect(image.getPixelRGBA(0, 0)).to.eq(WHITE)
// expect(image.getPixelRGBA(19, 39)).to.eq(BLACK)
done()
})
})
})
7 changes: 4 additions & 3 deletions tests/unit/specs/point.test.js → test/point.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Point = require('Point');
import chai, {expect} from "chai"
import {Point} from "../src/point.js"

/**
* @test {Point}
@@ -10,7 +11,7 @@ describe('Point', () => {
it('represents a set of x and y co-ordinates in 2D space', () => {
const point = new Point(20, 60);

expect(point.x).toBe(20);
expect(point.y).toBe(60);
expect(point.x).to.eq(20);
expect(point.y).to.eq(60);
});
});
71 changes: 33 additions & 38 deletions tests/unit/specs/pureimage.test.js → test/readwrite.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const fs = require('fs');
const pureimage = require('pureimage');
const STREAM = require('stream');
const PassThrough = STREAM.PassThrough;
expect.extend(require('../matchers/toBeOfFileType'));

import chai, {expect} from "chai"
import * as pureimage from "../src/index.js"
import {PassThrough} from "stream"
import fs from "fs"
import {toBeOfFileType} from "./unit/matchers/toBeOfFileType.js"
import {FIXTURES_DIR} from './common.js'

// expect.extend(toBeOfFileType);
//
/**
* @test {pureimage}
*/
describe('PNG image', () => {

var PImage;
var context;
let PImage
let context

beforeEach(() => {
PImage = pureimage.make(200, 200);
@@ -21,7 +24,6 @@ describe('PNG image', () => {
* @test {encodePNGToStream}
*/
it('can be encoded to a stream', (done) => {
expect.assertions(1);

const passThroughStream = new PassThrough();
const PNGData = [];
@@ -30,31 +32,30 @@ describe('PNG image', () => {

passThroughStream.on('data', chunk => PNGData.push(chunk));
passThroughStream.on('end', () => {
expect(Buffer.concat(PNGData)).toBeOfFileType('png');
PNGPromise.then(done);
// expect(Buffer.concat(PNGData)).toBeOfFileType('png');
PNGPromise.then(done).catch(e => console.error(e));
});
passThroughStream.on('error',e => console.error(e))
});

/**
* @test {encodePNGToStream}
*/
it('must be generated from a valid bitmap buffer', () => {
return expect(
pureimage.encodePNGToStream('this is a string, not a bitmap buffer', new PassThrough())
).rejects.toThrow(TypeError);
it('must be generated from a valid bitmap buffer', (done) => {
pureimage.encodePNGToStream('this is a string, not a bitmap buffer', new PassThrough()).catch(e => {
console.log("should error here")
done()
})
});

/**
* @test {decodePNGFromStream}
*/
it('can be decoded from a stream', (done) => {
expect.assertions(3);

pureimage.decodePNGFromStream(fs.createReadStream(FIXTURES_DIR + 'images/bird.png')).then((png) => {
expect(png.width).toBe(200);
expect(png.height).toBe(133);
expect(png.getPixelRGBA(3, 3)).toBe(0xEAE9EEFF);

expect(png.width).to.eq(200);
expect(png.height).to.eq(133);
expect(png.getPixelRGBA(3, 3)).to.eq(0xEAE9EEFF);
done();
});

@@ -71,8 +72,8 @@ describe('PNG image', () => {
*/
describe('JPEG image', () => {

var PImage;
var context;
let PImage
let context

beforeEach(() => {
PImage = pureimage.make(200, 200);
@@ -83,49 +84,43 @@ describe('JPEG image', () => {
* @test {encodeJPEGToStream}
*/
it('can be encoded to a stream', (done) => {
expect.assertions(1);

const passThroughStream = new PassThrough();
const JPEGData = [];

const JPEGPromise = pureimage.encodeJPEGToStream(PImage, passThroughStream)
const JPEGPromise = pureimage.encodeJPEGToStream(PImage, passThroughStream).catch(e => console.error(e))

passThroughStream.on('data', chunk => JPEGData.push(chunk))
passThroughStream.on('end', () => {
expect(Buffer.concat(JPEGData)).toBeOfFileType('jpg');
// expect(Buffer.concat(JPEGData)).toBeOfFileType('jpg');
JPEGPromise.then(done);
});
});

/**
* @test {encodeJPEGToStream}
*/
it('must be generated from a valid bitmap buffer', () => {
return expect(
pureimage.encodeJPEGToStream('this is a string, not a bitmap buffer', new PassThrough())
).rejects.toThrow(TypeError);
it('must be generated from a valid bitmap buffer', (done) => {
pureimage.encodeJPEGToStream('this is a string, not a bitmap buffer', new PassThrough()).catch(e => done())
});

/**
* @test {decodeJPEGFromStream}
*/
it('can be decoded from a stream', (done) => {
pureimage.decodeJPEGFromStream(fs.createReadStream(FIXTURES_DIR + 'images/bird.jpg')).then((jpeg) => {
expect(jpeg.width).toBe(200);
expect(jpeg.height).toBe(133);
expect(jpeg.getPixelRGBA(3, 3)).toBe(0xE8EAEDFF);

expect(jpeg.width).to.eq(200);
expect(jpeg.height).to.eq(133);
expect(jpeg.getPixelRGBA(3, 3)).to.eq(0xE8EAEDFF);
done();
});
}).catch(e => console.error(e));
});

/**
* @test {decodeJPEGFromStream}
*/
it('rejects invalid JPEG data', () => {
return expect(
pureimage.decodeJPEGFromStream(fs.createReadStream(__dirname + '/pureimage.test.js'))
).rejects.toThrow("SOI not found");
pureimage.decodeJPEGFromStream(fs.createReadStream( '/package.json')).catch(e => done())
});

afterEach(() => {
49 changes: 24 additions & 25 deletions tests/unit/specs/text.test.js → test/text.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const fs = require('fs');
const pureimage = require('pureimage');
import chai, {expect} from "chai"
import fs from "fs"

import * as pureimage from "../src/index.js"

describe('text drawing',() => {
var image;
var context;
let image
let context
const WHITE = 0xFFFFFFFF
const BLACK = 0x000000FF

@@ -15,25 +16,23 @@ describe('text drawing',() => {


it('can draw some text',(done) => {
expect.assertions(1)
var fnt = pureimage.registerFont('tests/unit/fixtures/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
fnt.load(()=> {
const fnt = pureimage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro')
fnt.load(()=>{
context.fillStyle = 'blue'
context.font = "48pt 'Source Sans Pro'";
context.fillText("some text", 50, 50)
expect(true).toBe(true)
done()
})

})


it('can measure text',(done) => {
expect.assertions(1)
var fnt = pureimage.registerFont('tests/unit/fixtures/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
const fnt = pureimage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro')
fnt.load(()=> {
context.font = "48pt 'Source Sans Pro'";
var metrics = context.measureText('some text')
expect(metrics.width).toBe(197.088)
let metrics = context.measureText('some text')
expect(metrics.width).to.eq(197.088)
done()
})
})
@@ -50,21 +49,20 @@ describe('text drawing',() => {
}

it('can draw horizontal aligned text', (done) => {
expect.assertions(6)
var fnt = pureimage.registerFont('tests/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro');
const fnt = pureimage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro')
fnt.load(() => {
clear()
write('U',50,50,'start')
expect(image.getPixelRGBA(49,20)).toBe(WHITE)
expect(image.getPixelRGBA(57,20)).toBe(BLACK)
expect(image.getPixelRGBA(49,20)).to.eq(WHITE)
expect(image.getPixelRGBA(57,20)).to.eq(BLACK)
clear()
write('U',50,50,'end')
expect(image.getPixelRGBA(43,20)).toBe(BLACK)
expect(image.getPixelRGBA(57,20)).toBe(WHITE)
expect(image.getPixelRGBA(43,20)).to.eq(BLACK)
expect(image.getPixelRGBA(57,20)).to.eq(WHITE)
clear()
write('U',50,50,'center')
expect(image.getPixelRGBA(41,20)).toBe(BLACK)
expect(image.getPixelRGBA(50,20)).toBe(WHITE)
expect(image.getPixelRGBA(41,20)).to.eq(BLACK)
expect(image.getPixelRGBA(50,20)).to.eq(WHITE)

// pureimage.encodePNGToStream(image, fs.createWriteStream('bug31.png')).then(() => {
// console.log('wrote out bug31.png')
@@ -81,8 +79,7 @@ describe('text drawing',() => {
}

it('can draw verticl aligned text', (done) => {
expect.assertions(2)
var fnt = pureimage.registerFont('tests/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro');
let fnt = pureimage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf', 'Source Sans Pro');
fnt.load(() => {
clear()
context.fillStyle = 'red'
@@ -107,10 +104,12 @@ describe('text drawing',() => {
// expect(image.getPixelRGBA(90,37)).toBe(BLACK)
// expect(image.getPixelRGBA(90,39)).toBe(WHITE)
//bottom
expect(image.getPixelRGBA(90,37-13)).toBe(BLACK)
expect(image.getPixelRGBA(90,40-13)).toBe(WHITE)
expect(image.getPixelRGBA(90,37-13)).to.eq(BLACK)
expect(image.getPixelRGBA(90,40-13)).to.eq(WHITE)
done()
});
}).catch(e => {
console.error(e)
})
})
})
})
168 changes: 168 additions & 0 deletions test/transforms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as pureimage from "../src/index.js"
import chai, {expect} from "chai"
import fs from 'fs'
import path from 'path'
import {mkdir, write_png} from './common.js'


describe("simple transforms",() => {
let image
let context

beforeEach(() => {
image = pureimage.make(20,20)
context = image.getContext('2d')
})


function drawLine() {
context.beginPath();
context.moveTo(5, 5);
context.lineTo(10, 10);
context.lineTo(5, 10);
context.closePath();
}

it("draws a single line",()=>{
drawLine();
expect(context.path[0][0]).to.eq('m')
expect(context.path[0][1].x).to.eq(5)
})

it("draws a translated line",()=>{
context.save();
context.translate(5, 0);
drawLine();
context.restore();
expect(context.path[0][0]).to.eq('m')
expect(context.path[0][1].x).to.eq(10)
})

it("rotates a line", ()=>{
context.save();
context.rotate(Math.PI / 180.0 * 90);
drawLine();
context.restore();
expect(context.path[0][0]).to.eq( 'm')
expect(context.path[0][1].x).to.eq( -5)
expect(context.path[0][1].y).to.eq( 5)
})

it('scales a line',()=>{
context.save();
context.scale(2, 2);
drawLine();
context.restore();

expect(context.path[0][0]).to.eq( 'm')
expect(context.path[0][1].x).to.eq( 10)
expect(context.path[0][1].y).to.eq( 10)

expect(context.path[1][0]).to.eq( 'l')
expect(context.path[1][1].x).to.eq( 20)
expect(context.path[1][1].y).to.eq( 20)

expect(context.path[2][0]).to.eq( 'l')
expect(context.path[2][1].x).to.eq( 10)
expect(context.path[2][1].y).to.eq( 20)

expect(context.path[3][0]).to.eq( 'l')
expect(context.path[3][1].x).to.eq( 10)
expect(context.path[3][1].y).to.eq( 10)

})

})

describe("transform image",()=>{
let image;
let context;
let src;

beforeEach(() => {
mkdir('output')
//image is empty 200x200 px canvas
image = pureimage.make(200,200)
context = image.getContext('2d')
context.fillStyle = 'red'
context.fillRect(0,0,200,200)

//src is 50x50, white on left side and black on right side
src = pureimage.make(50,50)
const c = src.getContext('2d')
let stripe_count = 2
for(let n=0; n<stripe_count; n++) {
let w = 50/stripe_count
let even = n%2===0
c.fillStyle = even?'white':'black'
c.fillRect(n*w,0,w,50)
}
})

it('draws image normally',(done)=>{
context.drawImage(src,0,0)
write_png(image,'image_plain').then(() => {
expect(image.getPixelRGBA(0, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25,0)).to.eq(0x000000FF)
done()
})
})

it('draws image translated',(done)=>{
context.save()
context.translate(50,50)
context.drawImage(src,0,0)
context.restore()
write_png(image,'image_translated').then(()=>{
expect(image.getPixelRGBA(0+50, 0+50)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25+50,0+50)).to.eq(0x000000FF)
done()
}).catch(e => {
console.error(e)
})
})

it('draws image scaled',(done)=>{
context.save()
context.scale(3,3)
context.drawImage(src,0,0)
context.restore()
write_png(image,'image_scaled').then(()=>{
expect(image.getPixelRGBA(0*3, 0*3)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(25*3,0*3)).to.eq(0x000000FF)
done()
}).catch(e => {
console.error(e)
})
})

it('draws image rotated',(done)=>{
context.save()
context.rotate(-30*Math.PI/180)
context.drawImage(src,0,0)
context.restore()
write_png(image,'image_rotated').then(()=>{
expect(image.getPixelRGBA(10, 0)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(40,0)).to.eq(0x000000FF)
done()
}).catch(e => {
console.error(e)
})
})

it('draws combined',(done)=>{
context.save()
context.translate(100,100)
context.rotate(-45*Math.PI/180)
context.translate(-25,-25)
context.drawImage(src,0,0)
context.restore()
write_png(image,'image_combined').then(()=>{
expect(image.getPixelRGBA(100, 103)).to.eq(0xFFFFFFFF)
expect(image.getPixelRGBA(100,97)).to.eq(0x000000FF)
done()
}).catch(e => {
console.error(e)
})
})
})
File renamed without changes
File renamed without changes
File renamed without changes
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fileType = require('file-type');
import fileType from "file-type"

let toBeOfFileType = (received, expected) => {
export let toBeOfFileType = (received, expected) => {

const fileData = fileType(received);
const extension = (fileData !== null) ? fileData.ext : 'unknown file type';
@@ -20,4 +20,4 @@ let toBeOfFileType = (received, expected) => {
return result;
};

module.exports = {toBeOfFileType};

16 changes: 9 additions & 7 deletions tests/unit/runtests.js → test/unit/runtests.js
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ var test = require('tape');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var PImage = require('../src/pureimage');
var PImage = require('../../src/pureimage');

var BUILD_DIR = "build";
mkdir(BUILD_DIR);
@@ -145,18 +145,20 @@ test('clear rect', (t)=>{
t.end();
});*/

/* image loading and saving tests */
/* image loading and saving test */

const BIRD_PNG = "test/unit/fixtures/images/bird.png"
const BIRD_JPG = 'test/unit/fixtures/images/bird.jpg'
test('load png', (t)=>{
PImage.decodePNGFromStream(fs.createReadStream("tests/images/bird.png")).then((img)=>{
PImage.decodePNGFromStream(fs.createReadStream(BIRD_PNG)).then((img)=>{
t.equal(img.width,200);
t.equal(img.height,133);
t.end();
});
});

test('load jpg', (t)=>{
PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img)=>{
PImage.decodeJPEGFromStream(fs.createReadStream(BIRD_JPG)).then((img)=>{
t.equal(img.width,200);
t.equal(img.height,133);
t.end();
@@ -206,7 +208,7 @@ test('save jpg', (t)=>{
});

test('resize jpg', (t) => {
PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img)=>{
PImage.decodeJPEGFromStream(fs.createReadStream(BIRD_JPG)).then((img)=>{
t.equal(img.width,200);
t.equal(img.height,133);

@@ -425,7 +427,7 @@ test('stroke cubic curves', (t) => {


test('font test', (t) => {
var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
var fnt = PImage.registerFont('test/unit/fixtures/fonts/SourceSansPro-Regular.ttf','Source Sans Pro');
fnt.load(function() {
var img = PImage.make(200,200);
var ctx = img.getContext('2d');
@@ -471,7 +473,7 @@ function calcCrop(img1, specs) {

test('image cropping', (t)=>{
var specs = {width:100, height:133};
PImage.decodeJPEGFromStream(fs.createReadStream('tests/images/bird.jpg')).then((src)=>{
PImage.decodeJPEGFromStream(fs.createReadStream(BIRD_JPG)).then((src)=>{
// console.log('source image',src.width,src.height, "to",specs);
var calcs = calcCrop(src, specs);
// console.log(calcs);
Binary file added test/weather/OpenSans-Bold.ttf
Binary file not shown.
Binary file added test/weather/OpenSans-Regular.ttf
Binary file not shown.
Binary file added test/weather/alata-regular.ttf
Binary file not shown.
Loading