'use strict' var t = require('tape') var fs = require('fs') var path = require('path') var getPixels = require('../') var match = require('pixelmatch') var toab = require('to-array-buffer') var fixture = require('./fixture') var ab2s = require('arraybuffer-to-string') var x = require('object-assign') var getNdPixels = require('get-pixels') var isOnline = require('is-online') var isBrowser = require('is-browser') var save = require('save-file') if (!isBrowser) { var path = require('path') var del = require('del') } if (!isBrowser) { // var { JSDOM } = require('jsdom') // var { window } = new JSDOM(`<!doctype html>`) } var clipFix = { data: [ 0,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255 ], width: 2, height: 2 } var pngFixData = toab(fixture.pngDataURL) var pngFixURL = fixture.pngURL const ASSERT_N = 19 const REQUEST_TIMEOUT = 3000 async function testSource(t, arg, o, fix=fixture) { // direct let to = setTimeout(function () {t.fail('Direct timeout')}, REQUEST_TIMEOUT) let data = await getPixels(arg, o) clearTimeout(to) t.equal(data.width, fix.width) t.equal(data.height, fix.height) fix.data ? t.equal(match(data.data, fix.data, null, fix.width, fix.height, {threshold: .006}), 0, 'Ok async pixels') : t.ok(data.data[0], 'Ok async pixels') let redata = await getPixels(data) t.equal(redata, data, 'Repeat data') // second time (cache) to = setTimeout(function () {t.fail('Direct second timeout')}, REQUEST_TIMEOUT) let data2 = await getPixels(arg, o) clearTimeout(to) t.deepEqual(data.data, data2.data) t.equal(data2.width, fix.width) t.equal(data2.height, fix.height) fix.data ? t.equal(match(data2.data, fix.data, null, fix.width, fix.height, {threshold: .006}), 0, 'Ok async pixels twice') : t.ok(data2.data[0], 'Ok async pixels twice') let redata2 = await getPixels(data2) t.equal(redata2, data2, 'Repeat data secondary') // clip to = setTimeout(function () {t.fail('Clip timeout')}, REQUEST_TIMEOUT) let clip = await getPixels(arg, x({clip: [1,1,3,3]}, o)) clearTimeout(to) t.equal(clip.width, 2) t.equal(clip.height, 2) fix.data ? t.equal(match(clip.data, clipFix.data, null, 2, 2, {threshold: .006}), 0, 'Ok clip pixels') : t.ok(clip.data[0], 'Ok clip pixels') // alltogether to = setTimeout(function () {t.fail('All timeout')}, REQUEST_TIMEOUT) var list = await getPixels.all([ o, x({clip: [1,1,3,3]}, o) ], {source: arg}) clearTimeout(to) t.deepEqual(data.data, list[0].data, 'Ok all pixels data') t.equal(list[0].width, fix.width) t.equal(list[0].height, fix.height) fix.data ? t.equal(match(list[0].data, fix.data, null, fix.width, fix.height, {threshold: .006}), 0, 'Ok all pixels') : t.ok(list[0].data[0], 'Ok all pixels') t.equal(list[1].width, 2) t.equal(list[1].height, 2) fix.data ? t.equal(match(list[1].data, clipFix.data, null, 2, 2, {threshold: .006}), 0, 'Ok clip pixels') : t.ok(list[1].data[0], 'Ok all clip pixels') } async function online () { if (isOnline.call) { isOnline = await isOnline() } return isOnline } // strings t('absolute path', async t => { t.plan(ASSERT_N) await testSource(t, path.resolve('./test/test_pattern.png')) t.end() }) t('relative path', async t => { t.plan(ASSERT_N) await testSource(t, './test/test_pattern.png') t.end() }) t('some path', async t => { t.plan(ASSERT_N) await testSource(t, 'test/test_pattern.png') t.end() }) t('https', async t => { if (await online()) { t.plan(ASSERT_N) await testSource(t, pngFixURL) } t.end() }) t('http', async t => { if (await online()) { t.plan(ASSERT_N) await testSource(t, pngFixURL.replace('https', 'http')) } t.end() }) t('protocol-relative URL', async t => { if (await online()) { t.plan(ASSERT_N) await testSource(t, pngFixURL.replace('https:', '')) } t.end() }) t('data URL', async t => { t.plan(2 * ASSERT_N) await testSource(t, fixture.pngDataURL) await testSource(t, fixture.jpgDataURL) t.end() }) t('base64', async t => { t.plan(ASSERT_N) await testSource(t, fixture.pngDataURL.replace(/^data:image\/(png|jpg);base64,/, '')) t.end() }) t('raw pixels base64', async t => { t.plan(ASSERT_N) await testSource(t, ab2s(fixture.data, 'base64'), {w:16, h:8}) t.end() }) // DOMs t(`<img>`, async t => { //TODO: add node test if (!isBrowser) return t.end() t.plan(ASSERT_N) let img = document.createElement('img') img.src = './test/test_pattern.png' await testSource(t, img) t.end() }) t(`<image>`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N) let el = document.createElement('div') el.innerHTML = `<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="./test/test_pattern.png"/> </svg> ` let img = el.firstChild.firstChild await testSource(t, img) t.end() }) t(`<video>`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N) let el = document.createElement('div') el.innerHTML = `<video src="./test/stream_of_water.webm"></video>` await testSource(t, el.firstChild, null, { width: 480, height: 360 }) t.end() }) t(`Video bad src`, async t => { if (!isBrowser) return t.end() t.plan(1) let el = document.createElement('div') el.innerHTML = `<video src="./test/xxx.webm"></video>` try { await getPixels(el.firstChild) } catch (e) { t.ok(e) } t.end() }) t(`Image`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N) let img = new Image img.src = './test/test_pattern.png' await testSource(t, img) t.end() }) t(`Image bad src`, async t => { if (!isBrowser) return t.end() t.plan(1) let img = new Image() img.src = 'xxx.png' try { await getPixels(img) } catch (e) { t.ok(e) } t.end() }) t(`ImageData`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N) var context = document.createElement('canvas').getContext('2d') var idata = context.createImageData(fixture.width, fixture.height) for (var i = 0; i < fixture.data.length; i++) { idata.data[i] = fixture.data[i] } await testSource(t, idata) t.end() }) t(`ImageBitmap`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N * 2) var canvas = fixture.canvas2d let bm = createImageBitmap(canvas) await testSource(t, bm) bm = await createImageBitmap(canvas) await testSource(t, bm) t.end() }) t(`File, Blob`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N * 2) await testSource(t, new File([pngFixData], 'file.png')) await testSource(t, new Blob([pngFixData])) t.end() }) t(`Canvas/Context2D`, async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N * 2) var canvas = fixture.canvas2d await testSource(t, canvas) var canvas = fixture.canvas2d await testSource(t, canvas.getContext('2d')) t.end() }) t(`Canvas/WebGLContext`, async t => { if (isBrowser) { t.plan(ASSERT_N * 4) await testSource(t, fixture.gl) await testSource(t, fixture.canvasGl) } else { t.plan(ASSERT_N * 3) await testSource(t, fixture.gl) } await testSource(t, fixture.regl) let c = fixture.gl.canvas Object.defineProperty(fixture.gl, 'canvas', { writable: true, configurable: true, value: null }) fixture.gl.canvas = null await testSource(t, fixture.gl) fixture.gl.canvas = c t.end() }) // buffers t(`Buffer`, async t => { t.plan(ASSERT_N + 1) var buf = new Buffer(fixture.data) try { if (getPixels.cache.get(fixture.data)) throw Error('cached') await getPixels(new Buffer(fixture.data)) } catch (e) { t.ok(e) } await testSource(t, buf, {width: fixture.width, height: fixture.height}) t.end() }) t(`ArrayBuffer`, async t => { t.plan(ASSERT_N + 1) try { if (getPixels.cache.get(fixture.data.buffer)) throw Error('cached') await getPixels(fixture.data.buffer) } catch (e) { t.ok(e) } await testSource(t, fixture.data.buffer, {width: fixture.width, height: fixture.height}) t.end() }) t(`Uint8Array`, async t => { t.plan(ASSERT_N + 1) try { if (getPixels.cache.get(fixture.data)) throw Error('cached') await getPixels(fixture.data) } catch (e) { t.ok(e) } await testSource(t, fixture.data, {width: fixture.width, height: fixture.height}) t.end() }) t(`Uint8Array encoded`, async t => { t.plan(ASSERT_N) await testSource(t, new Uint8Array(pngFixData)) t.end() }) t(`Uint8ClampedArray`, async t => { t.plan(ASSERT_N + 1) try { if (getPixels.cache.get(fixture.data)) throw Error('cached') await getPixels(new Uint8ClampedArray(fixture.data)) } catch (e) { t.ok(e) } await testSource(t, new Uint8ClampedArray(fixture.data), {width: fixture.width, height: fixture.height}) t.end() }) t(`Float32Array`, async t => { t.plan(ASSERT_N + 1) var arr = new Float32Array(fixture.data.length) for (let i = 0; i < arr.length; i++) { arr[i] = fixture.data[i] / 255 } try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) t(`Float64Array`, async t => { t.plan(ASSERT_N + 1) var arr = new Float64Array(fixture.data.length) for (let i = 0; i < arr.length; i++) { arr[i] = fixture.data[i] / 255 } try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) t(`Array`, async t => { t.plan(ASSERT_N + 1) var arr = Array.from(fixture.data) try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) t(`[[r,g,b,a], [r,g,b,a], ...]`, async t => { t.plan(ASSERT_N + 1) // [[r,g,b,a], [r,g,b,a], ...] var arr = Array(fixture.data.length / 4) for (let i = 0; i < arr.length; i++) { arr[i] = [ fixture.data[4 * i + 0], fixture.data[4 * i + 1], fixture.data[4 * i + 2], fixture.data[4 * i + 3] ] } try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) t('[[r,g,b,a,r,g,b,a], [r,g,b,a,r,g,b,a], ...]', async t => { t.plan(ASSERT_N + 1) // [[r,g,b,a], [r,g,b,a], ...] var arr = [] for (let y = 0; y < fixture.height; y++) { var row = [] for (let i = 0; i < fixture.width; i++) { row.push(fixture.data[y * fixture.width * 4 + i * 4]) row.push(fixture.data[y * fixture.width * 4 + i * 4 + 1]) row.push(fixture.data[y * fixture.width * 4 + i * 4 + 2]) row.push(fixture.data[y * fixture.width * 4 + i * 4 + 3]) } arr.push(row) } try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) t('[[[r,g,b,a], [r,g,b,a]], [[r,g,b,a], [r,g,b,a]], ...]', async t => { t.plan(ASSERT_N + 1) // [[r,g,b,a], [r,g,b,a], ...] var arr = [] for (let y = 0; y < fixture.height; y++) { var row = [] for (let i = 0; i < fixture.width; i++) { row.push([ fixture.data[y * fixture.width * 4 + i * 4], fixture.data[y * fixture.width * 4 + i * 4 + 1], fixture.data[y * fixture.width * 4 + i * 4 + 2], fixture.data[y * fixture.width * 4 + i * 4 + 3] ]) } arr.push(row) } try { await getPixels(arr) } catch (e) { t.ok(e) } await testSource(t, arr, {width: fixture.width, height: fixture.height}) t.end() }) // cases t(`options directly`, async t => { t.plan(ASSERT_N) await testSource(t, {source: fixture.pngDataURL}) t.end() }) t(`ndarray`, async t => { t.plan(ASSERT_N) getNdPixels(fixture.pngDataURL, async (e, px) => { await testSource(t, px) t.end() }) }) t(`multiple sources: list`, async t => { if (!(await online())) return t.end() t.plan(8) // different sources list let list = await getPixels.all([ fixture.data, fixture.pngDataURL, fixture.pngURL, fixture.gl ], {width: fixture.width, height: fixture.height}) t.equal(list[0].data.length, 512) t.equal(list[1].data.length, 512) t.equal(list[2].data.length, 512) t.equal(list[3].data.length, 512) t.equal(match(list[0].data, fixture.data, null, fixture.width, fixture.height, {threshold: .006}), 0, 'Ok data pixels') t.equal(match(list[1].data, fixture.data, null, fixture.width, fixture.height, {threshold: .006}), 0, 'Ok data pixels') t.equal(match(list[2].data, fixture.data, null, fixture.width, fixture.height, {threshold: .006}), 0, 'Ok data pixels') t.equal(match(list[3].data, fixture.data, null, fixture.width, fixture.height, {threshold: .006}), 0, 'Ok data pixels') t.end() }) t('multiple sources: dict', async t => { if (!(await online())) return t.end() t.plan(3) // different source dict let {a, b, c} = await getPixels.all({ a: fixture.data, b: fixture.pngDataURL, c: fixture.pngURL }, {width: fixture.width, height: fixture.height}) t.deepEqual(fixture.data, a.data) t.deepEqual(fixture.data, b.data) t.deepEqual(fixture.data, c.data) t.end() }) t('multiple source error', async t => { if (!(await online())) return t.end() t.plan(2) await getPixels.all([ fixture.data, fixture.pngDataURL, fixture.canvas ]).catch(e => { t.ok(e) }) await getPixels.all({ a: fixture.data, b: fixture.pngDataURL, c: fixture.canvas }).catch(e => { t.ok(e) }) t.end() }) t('<picture>', async t => { if (!isBrowser) return t.end() t.plan(ASSERT_N) let el = document.createElement('div') el.innerHTML = `<picture> <source srcset="${fixture.jpgDataURL}"> <img src="${fixture.pngDataURL}"> </picture>` await testSource(t, el.firstChild) t.end() }) t('bad string', async t => { t.plan(1) try { await getPixels('') } catch (e) { t.ok(e) } t.end() }) t('not existing url', async t => { t.plan(1) try { await getPixels('./test/xxx.png') } catch (e) { t.ok(e) } t.end() }) t('not an image url', async t => { t.plan(1) try { await getPixels('./test/fixture.js') } catch (e) { t.ok(e) } t.end() }) t.skip('#img-el', async t => { }) t('changed URL contents', async t => { if (isBrowser) return t.end() var data1 = fixture.pngDataURL var data2 = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAICAYAAADwdn+XAAAAMklEQVQoU2P8z8DwnwEEQCQjnAcWIgYwMvwHaQUTZAFGBob///+TqxvsaIq0jxoAijYA90Mg6YIzCEUAAAAASUVORK5CYII=` // cached await save( toab(data1), './a.png' ) var result1 = await getPixels('./a.png') t.deepEqual(result1.data, fixture.data) await save( toab(data2), './a.png' ) var result2 = await getPixels('./a.png') t.deepEqual(result2.data, result1.data) // uncached await save( toab(data1), './b.png' ) var result1 = await getPixels('./b.png', {cache: false}) t.deepEqual(result1.data, fixture.data) await save( toab(data2), './b.png' ) var result2 = await getPixels('./b.png') t.notDeepEqual(result2.data, result1.data) del(['./b.png', './a.png']) t.end() }) t.skip('URL timeout') t.skip('bad URL data') t.skip('malformed encoded buffer') t.skip(`File, Blob with encoded data`, async t => { t.plan(ASSERT_N * 2) await testSource(t, new File([fixture.data], 'file.png')) await testSource(t, new Blob([fixture.data])) t.end() }) t.skip('Stream') t.skip('SourceBuffer') t.skip('SourceBufferList') t.skip(`MediaSource`, async t => { t.plan(ASSERT_N) // var mediaSource = new MediaSource() // var video = new HTMLVideoElement() // video.src = URL.createObjectURL(mediaSource) // mediaSource.addEventListener('sourceopen', function () { // mediaSource.addSourceBuffer(mimeCodec) // }) // await testSource(t, new) t.end() }) t.skip(`OffscreenCanvas, bitmaprenderer`, async t => { t.plan(ASSERT_N * 2) // let offscreen = new OffscreenCanvas(fixture.width, fixture.height) // let context = offscreen.getContext('webgl') // ... some drawing for the first canvas using the gl context ... // Commit rendering to the first canvas // var bm = offscreen.transferToImageBitmap() // one.transferImageBitmap(bm); }) t.skip('object with float data array', async t => { // FIXME: probably this is normal behaviour let data = [0,0,0,1, 1,1,1,1, 1,1,1,1, 0,0,0,1] let px = await getPixels({ data: data, width: 2, height: 2 }) t.deepEqual(px.data, [0,0,0,255, 255,255,255,255, 255,255,255,255, 0,0,0,255]) t.end() }) t('do not cache arrays', async t => { // FIXME: it does not cache array, but transparently returns input value, unless indicater otherwise. Should that return copy instead? var data = fixture.data.slice() var result1 = await getPixels({data, w: fixture.width, h: fixture.height}) data[10] = 255 var result2 = await getPixels({data, w: fixture.width, h: fixture.height}) t.notDeepEqual(result1.data, result2.data) t.end() }) t('error during processing', t => { t.plan(1) getPixels(/asd/).then(null, err => { t.ok(err) t.end() }) }) t('tagged template', async t => { let {data} = await getPixels`./test/test_pattern.png` t.ok(data.length) t.end() }) // get-pixels cases function test_image (t, pixels) { t.equal(match(pixels.data, fixture.data, null, fixture.width, fixture.height, {threshold: 0}), 0) // t.deepEqual(pixels.data, fixture.data) } t('get-pixels', async function(t) { getPixels('test/lena.png', function(err, pixels) { if(err) { t.fail(err) } else { t.equals([pixels.width, pixels.height].join(','), '512,512') } t.end() }) }) t('get-pixels-png', async function(t) { getPixels('test/test_pattern.png', function(err, pixels) { if(err) { t.error(err, 'failed to parse png') t.end() return } test_image(t, pixels) t.end() }) }) t.skip('get-pixels-ppm', async function(t) { getPixels(path.join(__dirname, 'test_pattern.ppm'), function(err, pixels) { if(err) { t.error(err, 'failed to parse ppm') t.end() return } test_image(t, pixels) t.end() }) }) t('get-pixels-gif', async function(t) { getPixels('test/test_pattern.gif', function(err, pixels) { if(err) { t.error(err, 'failed to parse gif') t.end() return } test_image(t, pixels) t.end() }) }) t('get-pixels-bmp', async function(t) { getPixels('test/test_pattern.bmp', function(err, pixels) { if(err) { t.error(err, 'failed to parse bmp') t.end() return } test_image(t, pixels) t.end() }) }) t('data url', async function(t) { var url = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7' getPixels(url, function(err, data) { if(err) { t.error('failed to read data url') t.end() return } t.ok(true, 'data url opened without crashing') t.end() }) }) t('get-pixels-buffer', async function(t) { var buffer = fs.readFileSync(__dirname + '/test_pattern.png') getPixels(buffer, 'image/png', function(err, pixels) { if(err) { t.error(err, 'failed to parse buffer') t.end() return } test_image(t, pixels) t.end() }) }) t('get-url png img', async function(t) { if (!(await online())) return t.end() var url = 'https://raw.githubusercontent.com/dy/get-pixel-data/master/test/test_pattern.png'; getPixels(url, function(err, pixels){ if(err) { t.error(err, 'failed to read web image data'); t.end(); return; } test_image(t, pixels); t.end(); }); }) t('get-url gif img', async function(t) { if (!(await online())) return t.end() var url = 'https://raw.githubusercontent.com/dy/get-pixel-data/master/test/test_pattern.gif'; getPixels(url, function(err, pixels){ if(err) { t.error(err, 'failed to read web image data'); t.end(); return; } test_image(t, pixels); t.end(); }); })