├── .yarnrc.yml ├── .github ├── FUNDING.yml ├── SECURITY.md └── workflows │ └── ci.yml ├── renovate.json ├── .gitignore ├── test ├── node.test.cjs ├── browser.test.cjs └── common.test.cjs ├── tsconfig.json ├── biome.json ├── src ├── node-get-pixels.ts ├── browser-get-pixels.ts ├── browser-save-pixels.ts ├── node-save-pixels.ts ├── common.ts └── index.ts ├── LICENSE ├── package.json └── README.md /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [donmccurdy] 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | {"extends": ["github>donmccurdy/renovate-config"]} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .yarn/* 4 | !.yarn/cache 5 | !.yarn/patches 6 | !.yarn/plugins 7 | !.yarn/releases 8 | !.yarn/sdks 9 | !.yarn/versions 10 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /test/node.test.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | const { getPixels, savePixels } = require('../dist/ndarray-pixels-node.cjs'); 4 | require('./common.test.cjs')('node', getPixels, savePixels); 5 | -------------------------------------------------------------------------------- /test/browser.test.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | global.regeneratorRuntime = require('regenerator-runtime'); 4 | const { getPixels, savePixels } = require('../dist/ndarray-pixels-browser.cjs'); 5 | require('./common.test.cjs')('browser', getPixels, savePixels); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "esModuleInterop": true, 5 | "lib": ["es6", "dom"], 6 | "target": "es6", 7 | "declaration": true, 8 | "types": ["node"], 9 | "typeRoots": ["node_modules/@types"], 10 | "strict": true, 11 | "skipLibCheck": true 12 | }, 13 | "include": ["src/*"] 14 | } 15 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatter": { 3 | "indentStyle": "tab", 4 | "indentWidth": 4, 5 | "lineWidth": 100 6 | }, 7 | "linter": { 8 | "rules": { 9 | "style": { 10 | "noNonNullAssertion": "off" 11 | } 12 | } 13 | }, 14 | "javascript": { 15 | "formatter": { 16 | "quoteStyle": "single" 17 | } 18 | }, 19 | "json": { 20 | "formatter": { 21 | "indentStyle": "space", 22 | "indentWidth": 2 23 | } 24 | }, 25 | "files": { 26 | "ignore": ["coverage/**", "dist/**", "package.json"] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/node-get-pixels.ts: -------------------------------------------------------------------------------- 1 | import ndarray, { type NdArray } from 'ndarray'; 2 | import sharp from 'sharp'; 3 | 4 | export async function getPixelsInternal( 5 | buffer: Uint8Array, 6 | _mimeType: string, 7 | ): Promise> { 8 | // Warn for Data URIs, URLs, and file paths. Support removed in v3. 9 | if (!(buffer instanceof Uint8Array)) { 10 | throw new Error('[ndarray-pixels] Input must be Uint8Array or Buffer.'); 11 | } 12 | 13 | const { data, info } = await sharp(buffer) 14 | .ensureAlpha() 15 | .raw() 16 | .toBuffer({ resolveWithObject: true }); 17 | 18 | return ndarray( 19 | new Uint8Array(data), 20 | [info.width, info.height, 4], 21 | [4, (4 * info.width) | 0, 1], 22 | 0, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [v20, v22] 16 | env: 17 | CI: true 18 | LINT: ${{ matrix.node-version == 'v22' && true || false }} 19 | steps: 20 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: corepack enable 26 | - run: yarn install 27 | - run: yarn build 28 | - run: yarn test:node 29 | - run: yarn test:browser 30 | - run: yarn lint:ci 31 | if: ${{ env.LINT == 'true' }} 32 | -------------------------------------------------------------------------------- /src/browser-get-pixels.ts: -------------------------------------------------------------------------------- 1 | import ndarray from 'ndarray'; 2 | import type { NdArray } from 'ndarray'; 3 | 4 | export function getPixelsInternal( 5 | buffer: Uint8Array, 6 | mimeType: string, 7 | ): Promise> { 8 | // Warn for Data URIs, URLs, and file paths. Support removed in v3. 9 | if (!(buffer instanceof Uint8Array)) { 10 | throw new Error('[ndarray-pixels] Input must be Uint8Array or Buffer.'); 11 | } 12 | 13 | const blob = new Blob([buffer], { type: mimeType }); 14 | return createImageBitmap(blob, { 15 | premultiplyAlpha: 'none', 16 | colorSpaceConversion: 'none', 17 | }).then((img) => { 18 | const canvas = new OffscreenCanvas(img.width, img.height); 19 | const context = canvas.getContext('2d')!; 20 | context.drawImage(img, 0, 0); 21 | const pixels = context.getImageData(0, 0, img.width, img.height); 22 | return ndarray( 23 | new Uint8Array(pixels.data), 24 | [img.width, img.height, 4], 25 | [4, 4 * img.width, 1], 26 | 0, 27 | ); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/browser-save-pixels.ts: -------------------------------------------------------------------------------- 1 | import type { NdArray } from 'ndarray'; 2 | import { putPixelData } from './common'; 3 | import type { ImageEncodeOptions } from './common'; 4 | 5 | export async function savePixelsInternal( 6 | pixels: NdArray, 7 | options: ImageEncodeOptions, 8 | ): Promise { 9 | // Create OffscreenCanvas and write pixel data. 10 | const canvas = new OffscreenCanvas(pixels.shape[0], pixels.shape[1]); 11 | 12 | const context = canvas.getContext('2d')!; 13 | const imageData = context.getImageData(0, 0, canvas.width, canvas.height); 14 | 15 | putPixelData(pixels, imageData.data); 16 | context.putImageData(imageData, 0, 0); 17 | 18 | return streamCanvas(canvas, options); 19 | } 20 | 21 | /** Creates readable stream from given OffscreenCanvas and options. */ 22 | async function streamCanvas( 23 | canvas: OffscreenCanvas, 24 | options: ImageEncodeOptions, 25 | ): Promise { 26 | const blob = await canvas.convertToBlob(options); 27 | const ab = await blob.arrayBuffer(); 28 | return new Uint8Array(ab); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Don McCurdy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/node-save-pixels.ts: -------------------------------------------------------------------------------- 1 | import type { NdArray } from 'ndarray'; 2 | import sharp, { type FormatEnum } from 'sharp'; 3 | import { putPixelData } from './common'; 4 | import type { ImageEncodeOptions } from './common'; 5 | 6 | export async function savePixelsInternal( 7 | pixels: NdArray, 8 | options: ImageEncodeOptions, 9 | ): Promise { 10 | const [width, height, channels] = pixels.shape as [number, number, 4]; 11 | const data = putPixelData(pixels, new Uint8Array(width * height * channels)); 12 | 13 | const { type, quality } = options; 14 | const format = (type ?? 'image/png').replace('image/', '') as keyof FormatEnum; 15 | 16 | const sharpOptions = { 17 | // Applicable to most formats. 18 | // Where used, an integer between 1 and 100 19 | quality: typeof quality === 'number' ? Math.round(1 + quality * 99) : undefined, 20 | // applicable to some formats, notably webp, avif 21 | lossless: quality === 1, 22 | // if this flag is true or unset, sharp interprets the `quality` flag to mean 23 | // that we want lossy color quantization. 24 | palette: false, 25 | }; 26 | 27 | return sharp(data, { raw: { width, height, channels } }) 28 | .toFormat(format, sharpOptions) 29 | .toBuffer(); 30 | } 31 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | import ndarray, { type NdArray } from 'ndarray'; 2 | import ops from 'ndarray-ops'; 3 | 4 | export interface ImageEncodeOptions { 5 | type?: string; 6 | quality?: number; 7 | } 8 | 9 | export function putPixelData( 10 | array: NdArray, 11 | data: Uint8Array | Uint8ClampedArray, 12 | frame = -1, 13 | ): Uint8Array | Uint8ClampedArray { 14 | if (array.shape.length === 4) { 15 | return putPixelData(array.pick(frame), data, 0); 16 | } 17 | 18 | if (array.shape.length === 3) { 19 | if (array.shape[2] === 3) { 20 | ops.assign( 21 | ndarray(data, [array.shape[0], array.shape[1], 3], [4, 4 * array.shape[0], 1]), 22 | array, 23 | ); 24 | ops.assigns(ndarray(data, [array.shape[0] * array.shape[1]], [4], 3), 255); 25 | } else if (array.shape[2] === 4) { 26 | ops.assign( 27 | ndarray(data, [array.shape[0], array.shape[1], 4], [4, array.shape[0] * 4, 1]), 28 | array, 29 | ); 30 | } else if (array.shape[2] === 1) { 31 | ops.assign( 32 | ndarray(data, [array.shape[0], array.shape[1], 3], [4, 4 * array.shape[0], 1]), 33 | ndarray( 34 | array.data, 35 | [array.shape[0], array.shape[1], 3], 36 | [array.stride[0], array.stride[1], 0], 37 | array.offset, 38 | ), 39 | ); 40 | ops.assigns(ndarray(data, [array.shape[0] * array.shape[1]], [4], 3), 255); 41 | } else { 42 | throw new Error('[ndarray-pixels] Incompatible array shape.'); 43 | } 44 | } else if (array.shape.length === 2) { 45 | ops.assign( 46 | ndarray(data, [array.shape[0], array.shape[1], 3], [4, 4 * array.shape[0], 1]), 47 | ndarray( 48 | array.data, 49 | [array.shape[0], array.shape[1], 3], 50 | [array.stride[0], array.stride[1], 0], 51 | array.offset, 52 | ), 53 | ); 54 | ops.assigns(ndarray(data, [array.shape[0] * array.shape[1]], [4], 3), 255); 55 | } else { 56 | throw new Error('[ndarray-pixels] Incompatible array shape.'); 57 | } 58 | 59 | return data; 60 | } 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { NdArray } from 'ndarray'; 2 | import { getPixelsInternal } from './node-get-pixels'; 3 | import { savePixelsInternal } from './node-save-pixels'; 4 | 5 | /** 6 | * Decodes image data to an `ndarray`. 7 | * 8 | * MIME type is optional when given a path or URL, and required when given a Uint8Array. 9 | * 10 | * Accepts `image/png` or `image/jpeg` in Node.js, and additional formats on browsers with 11 | * the necessary support in Canvas 2D. 12 | * 13 | * @param data 14 | * @param mimeType `image/jpeg`, `image/png`, etc. 15 | * @returns 16 | */ 17 | async function getPixels(data: Uint8Array, mimeType: string): Promise> { 18 | return getPixelsInternal(data, mimeType); 19 | } 20 | 21 | /** 22 | * Encodes an `ndarray` as image data in the given format. 23 | * 24 | * If the source `ndarray` was constructed manually with default stride, use 25 | * `ndarray.transpose(1, 0)` to reshape it and ensure an identical result from getPixels(). For an 26 | * ndarray created by getPixels(), this isn't necessary. 27 | * 28 | * Accepts `image/png` or `image/jpeg` in Node.js, and additional formats on browsers with 29 | * the necessary support in Canvas 2D. 30 | * 31 | * @param pixels ndarray of shape W x H x 4. 32 | * @param typeOrOptions object with encoding options or just the type 33 | * @param typeOrOptions.type target format (`image/jpeg`, `image/png`, `image/webp`, etc.) 34 | * @param typeOrOptions.quality quality as a number from 0 to 1, inclusive 35 | * @returns 36 | */ 37 | async function savePixels( 38 | pixels: NdArray, 39 | typeOrOptions: string | { type?: string; quality?: number }, 40 | ): Promise { 41 | let options: { type?: string; quality?: number }; 42 | if (typeof typeOrOptions === 'string') { 43 | options = { 44 | type: typeOrOptions, 45 | quality: undefined, 46 | }; 47 | } else { 48 | options = { 49 | type: typeOrOptions.type, 50 | quality: typeOrOptions.quality, 51 | }; 52 | } 53 | return savePixelsInternal(pixels, options); 54 | } 55 | 56 | export { getPixels, savePixels }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ndarray-pixels", 3 | "version": "5.0.1", 4 | "description": "ndarray-pixels", 5 | "type": "module", 6 | "sideEffects": false, 7 | "types": "./dist/index.d.ts", 8 | "main": "./dist/ndarray-pixels-node.cjs", 9 | "module": "./dist/ndarray-pixels-browser.modern.js", 10 | "exports": { 11 | "types": "./dist/index.d.ts", 12 | "node": { 13 | "require": "./dist/ndarray-pixels-node.cjs", 14 | "default": "./dist/ndarray-pixels-node.modern.js" 15 | }, 16 | "default": { 17 | "require": "./dist/ndarray-pixels-browser.cjs", 18 | "default": "./dist/ndarray-pixels-browser.modern.js" 19 | } 20 | }, 21 | "repository": "github:donmccurdy/ndarray-pixels", 22 | "author": "Don McCurdy ", 23 | "license": "MIT", 24 | "scripts": { 25 | "build": "yarn build:node && yarn build:browser", 26 | "build:node": "microbundle build --raw --no-compress --target node --format modern,cjs src/index.ts --output dist/ndarray-pixels-node.js", 27 | "build:browser": "microbundle build --raw --no-compress --target web --format modern,cjs src/index.ts --output dist/ndarray-pixels-browser.js --alias ./node-get-pixels=./browser-get-pixels.ts,./node-save-pixels=./browser-save-pixels.ts", 28 | "clean": "rm -rf dist/* || true", 29 | "test": "yarn test:node && yarn test:browser", 30 | "test:node": "tape test/node.test.cjs | tap-spec", 31 | "test:browser": "browserify test/browser.test.cjs | tape-run | tap-spec", 32 | "lint": "biome check src test", 33 | "lint:ci": "biome ci src test", 34 | "release": "yarn postversion:commit && yarn npm publish && yarn postversion:push", 35 | "prerelease": "yarn postversion:commit && yarn npm publish --tag alpha && yarn postversion:push", 36 | "postversion:commit": "git add -u && git commit -m \"chore(release): v$npm_package_version\" && git tag -a v$npm_package_version -m v$npm_package_version", 37 | "postversion:push": "git push && git push --tags", 38 | "prepublish": "yarn lint && yarn test", 39 | "prepack": "yarn clean && yarn build" 40 | }, 41 | "dependencies": { 42 | "@types/ndarray": "^1.0.14", 43 | "ndarray": "^1.0.19", 44 | "ndarray-ops": "^1.2.2", 45 | "sharp": "^0.34.0" 46 | }, 47 | "devDependencies": { 48 | "@biomejs/biome": "^1.9.4", 49 | "@types/ndarray-ops": "1.2.7", 50 | "@types/node": "22.15.3", 51 | "@types/tape": "5.8.1", 52 | "browserify": "17.0.1", 53 | "microbundle": "0.15.1", 54 | "regenerator-runtime": "0.14.1", 55 | "source-map-support": "0.5.21", 56 | "tap-spec": "5.0.0", 57 | "tape": "5.9.0", 58 | "tape-run": "11.0.0", 59 | "typescript": "5.8.3" 60 | }, 61 | "files": [ 62 | "dist/", 63 | "src/", 64 | "README.md", 65 | "LICENSE", 66 | "package.json" 67 | ], 68 | "packageManager": "yarn@4.10.3" 69 | } 70 | -------------------------------------------------------------------------------- /test/common.test.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | require('source-map-support').install(); 4 | 5 | const test = require('tape'); 6 | const ndarray = require('ndarray'); 7 | 8 | module.exports = (platform, getPixels, savePixels) => { 9 | test(`ndarray-pixels | ${platform}`, async (t) => { 10 | const pixelsIn = ndarray( 11 | new Uint8Array([255, 0, 0, 255, 0, 0, 0, 255, 255, 0, 0, 255, 0, 0, 0, 255]), 12 | [2, 2, 4], 13 | [4, 2 * 4, 1], // https://github.com/scijs/get-pixels/issues/52 14 | ); 15 | 16 | const data = await savePixels(pixelsIn, 'image/png'); 17 | const pixelsOut = await getPixels(Buffer.from(data), 'image/png'); 18 | 19 | t.deepEqual(pixelsIn.shape, pixelsOut.shape, 'ndarray.shape'); 20 | t.deepEqual(Array.from(pixelsOut.data), Array.from(pixelsIn.data), 'ndarray.data'); 21 | t.end(); 22 | }); 23 | 24 | test(`ndarray-pixels (webp lossless) | ${platform}, async ()`, async (t) => { 25 | const width = 7; 26 | const height = 13; 27 | const pixelsIn = ndarray(new Uint8Array(width * height * 4), [width, height, 4]).transpose( 28 | 1, 29 | 0, 30 | ); 31 | for (let i = 0; i < pixelsIn.shape[0]; ++i) { 32 | for (let j = 0; j < pixelsIn.shape[1]; ++j) { 33 | pixelsIn.set(i, j, 0, 255 * (i / pixelsIn.shape[0])); 34 | pixelsIn.set(i, j, 1, 255 * (j / pixelsIn.shape[1])); 35 | pixelsIn.set(i, j, 3, 255); 36 | } 37 | } 38 | 39 | const data = await savePixels(pixelsIn, { type: 'image/webp', quality: 1 }); 40 | const pixelsOut = await getPixels(Buffer.from(data), 'image/webp'); 41 | 42 | t.deepEqual(pixelsIn.shape, pixelsOut.shape, 'ndarray.shape'); 43 | t.deepEqual(Array.from(pixelsOut.data), Array.from(pixelsIn.data), 'ndarray.data'); 44 | t.end(); 45 | }); 46 | 47 | test(`ndarray-pixels (webp lossy) | ${platform}, async ()`, async (t) => { 48 | const width = 7; 49 | const height = 13; 50 | const pixelsIn = ndarray(new Uint8Array(width * height * 4), [width, height, 4]).transpose( 51 | 1, 52 | 0, 53 | ); 54 | for (let i = 0; i < pixelsIn.shape[0]; ++i) { 55 | for (let j = 0; j < pixelsIn.shape[1]; ++j) { 56 | pixelsIn.set(i, j, 0, 255 * (i / pixelsIn.shape[0])); 57 | pixelsIn.set(i, j, 1, 255 * (j / pixelsIn.shape[1])); 58 | pixelsIn.set(i, j, 3, 255); 59 | } 60 | } 61 | 62 | const data = await savePixels(pixelsIn, { type: 'image/webp', quality: 0.9 }); 63 | const pixelsOut = await getPixels(Buffer.from(data), 'image/webp'); 64 | 65 | t.deepEqual(pixelsIn.shape, pixelsOut.shape, 'ndarray.shape'); 66 | t.equal(pixelsOut.data.length, pixelsIn.data.length); 67 | let distance = 0; 68 | for (let i = 0; i < pixelsOut.data.length; ++i) { 69 | distance += Math.abs(pixelsOut.data[i] - pixelsIn.data[i]) / 255; 70 | } 71 | distance /= width * height; 72 | 73 | t.assert(0 < distance); // lossy 74 | t.assert(distance < 0.05, `mean pixel distance = ${distance}`); // but not overly lossy 75 | t.end(); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndarray-pixels 2 | 3 | [![Latest NPM release](https://img.shields.io/npm/v/ndarray-pixels.svg)](https://www.npmjs.com/package/ndarray-pixels) 4 | [![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/donmccurdy/ndarray-pixels/blob/main/LICENSE) 5 | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/ndarray-pixels)](https://bundlephobia.com/package/ndarray-pixels) 6 | [![CI](https://github.com/donmccurdy/ndarray-pixels/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/donmccurdy/ndarray-pixels/actions?query=workflow%3ACI) 7 | 8 | Convert [ndarray](https://www.npmjs.com/package/ndarray) ↔ image data, on Web and Node.js. 9 | 10 | Designed to be used with [other ndarray-based packages](http://scijs.net/packages/). 11 | 12 | ## Supported Formats 13 | 14 | | Platform | JPEG | PNG | Other | 15 | |----------|------|-----|-------------------------------------------------------------------------------------------------------| 16 | | Node.js | ✅ | ✅ | Based on [sharp support](https://sharp.pixelplumbing.com/) | 17 | | Web | ✅ | ✅ | Based on [browser support](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) | 18 | 19 | ### Known Bugs 20 | 21 | - [ ] Web implementation (Canvas 2D) premultiplies alpha. 22 | 23 | ## Quickstart 24 | 25 | ``` 26 | npm install --save ndarray-pixels 27 | ``` 28 | 29 | ### Web 30 | 31 | ```javascript 32 | import { getPixels, savePixels } from 'ndarray-pixels'; 33 | 34 | const bytesIn = await fetch('./input.png') 35 | .then((res) => res.arrayBuffer()) 36 | .then((arrayBuffer) => new Uint8Array(arrayBuffer)); 37 | 38 | // read 39 | const pixels = await getPixels(bytesIn, 'image/png'); // Uint8Array -> ndarray 40 | 41 | // modify 42 | const [width, height] = pixels.shape; 43 | for (let x = 0; x < width; ++x) { 44 | for (let y = 0; y < height; ++y) { 45 | pixels.set(x, y, 0, 255); // R 46 | pixels.set(x, y, 1, 0.0); // G 47 | pixels.set(x, y, 2, 0.0); // B 48 | pixels.set(x, y, 3, 255); // A 49 | } 50 | } 51 | 52 | // write 53 | const bytesOut = await savePixels(pixels, 'image/png'); // ndarray -> Uint8Array 54 | ``` 55 | 56 | 57 | ### Node.js 58 | 59 | ```javascript 60 | const fs = require('fs'); 61 | const { getPixels, savePixels } = require('ndarray-pixels'); 62 | 63 | const bufferIn = fs.readFileSync('./input.png'); 64 | 65 | // read 66 | const pixels = await getPixels(bufferIn, 'image/png'); // Uint8Array -> ndarray 67 | 68 | // modify 69 | const [width, height] = pixels.shape; 70 | for (let x = 0; x < width; ++x) { 71 | for (let y = 0; y < height; ++y) { 72 | pixels.set(x, y, 0, 255); // R 73 | pixels.set(x, y, 1, 0.0); // G 74 | pixels.set(x, y, 2, 0.0); // B 75 | pixels.set(x, y, 3, 255); // A 76 | } 77 | } 78 | 79 | // write 80 | const bufferOut = await savePixels(pixels, 'image/png'); // ndarray -> Uint8Array 81 | fs.writeFileSync('./output.png', bufferOut); 82 | ``` 83 | 84 | ## API 85 | 86 | ### Function: getPixels() 87 | 88 | > **getPixels**(`data`, `mimeType`): `Promise`\<`NdArray`\<`Uint8Array`\<`ArrayBufferLike`\>\>\> 89 | 90 | Decodes image data to an `ndarray`. 91 | 92 | MIME type is optional when given a path or URL, and required when given a Uint8Array. 93 | 94 | Accepts `image/png` or `image/jpeg` in Node.js, and additional formats on browsers with 95 | the necessary support in Canvas 2D. 96 | 97 | #### Parameters 98 | 99 | ##### data 100 | 101 | `Uint8Array` 102 | 103 | ##### mimeType 104 | 105 | `string` 106 | 107 | `image/jpeg`, `image/png`, etc. 108 | 109 | #### Returns 110 | 111 | `Promise`\<`NdArray`\<`Uint8Array`\<`ArrayBufferLike`\>\>\> 112 | ### Function: savePixels() 113 | 114 | > **savePixels**(`pixels`, `typeOrOptions`): `Promise`\<`Uint8Array`\<`ArrayBufferLike`\>\> 115 | 116 | Encodes an `ndarray` as image data in the given format. 117 | 118 | If the source `ndarray` was constructed manually with default stride, use 119 | `ndarray.transpose(1, 0)` to reshape it and ensure an identical result from getPixels(). For an 120 | ndarray created by getPixels(), this isn't necessary. 121 | 122 | Accepts `image/png` or `image/jpeg` in Node.js, and additional formats on browsers with 123 | the necessary support in Canvas 2D. 124 | 125 | #### Parameters 126 | 127 | ##### pixels 128 | 129 | `NdArray`\<`Uint8Array`\<`ArrayBufferLike`\> \| `Uint8ClampedArray`\<`ArrayBufferLike`\>\> 130 | 131 | ndarray of shape W x H x 4. 132 | 133 | ##### typeOrOptions 134 | 135 | object with encoding options or just the type 136 | 137 | `string` | 138 | 139 | \{ `quality?`: `number`; `type?`: `string`; \} 140 | 141 | object with encoding options or just the type 142 | 143 | ###### quality? 144 | 145 | `number` 146 | 147 | quality as a number from 0 to 1, inclusive 148 | 149 | ###### type? 150 | 151 | `string` 152 | 153 | target format (`image/jpeg`, `image/png`, `image/webp`, etc.) 154 | 155 | #### Returns 156 | 157 | `Promise`\<`Uint8Array`\<`ArrayBufferLike`\>\> 158 | --------------------------------------------------------------------------------