From 9bac5052705a7f815de55ed865d8c3c765f36329 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 2 Nov 2016 12:32:16 -0700 Subject: [PATCH] cleaner handling of image data --- js/source/image_source.js | 2 +- js/style/image_sprite.js | 24 ++++++++++++++---------- js/symbol/sprite_atlas.js | 6 +++--- js/util/ajax.js | 10 +--------- js/util/browser.js | 9 +++++++++ test/suite_implementation.js | 18 +++++++----------- 6 files changed, 35 insertions(+), 34 deletions(-) diff --git a/js/source/image_source.js b/js/source/image_source.js index 846a9516892..31b6425ed3a 100644 --- a/js/source/image_source.js +++ b/js/source/image_source.js @@ -144,7 +144,7 @@ class ImageSource extends Evented { } prepare() { - if (!this.tile || !this._loaded || !this.image || !this.image.complete) return; + if (!this.tile || !this._loaded || !this.image) return; this._prepareImage(this.map.painter.gl, this.image); } diff --git a/js/style/image_sprite.js b/js/style/image_sprite.js index 5f8200d5028..7a2cecf34e0 100644 --- a/js/style/image_sprite.js +++ b/js/style/image_sprite.js @@ -32,7 +32,7 @@ class ImageSprite extends Evented { } this.data = data; - if (this.img) this.fire('data', {dataType: 'style'}); + if (this.imgData) this.fire('data', {dataType: 'style'}); }); ajax.getImage(normalizeURL(base, format, '.png'), (err, img) => { @@ -41,15 +41,18 @@ class ImageSprite extends Evented { return; } + this.imgData = browser.getImageData(img); + // premultiply the sprite - for (let i = 0; i < img.data.length; i += 4) { - const alpha = img.data[i + 3] / 255; - img.data[i + 0] *= alpha; - img.data[i + 1] *= alpha; - img.data[i + 2] *= alpha; + for (let i = 0; i < this.imgData.length; i += 4) { + const alpha = this.imgData[i + 3] / 255; + this.imgData[i + 0] *= alpha; + this.imgData[i + 1] *= alpha; + this.imgData[i + 2] *= alpha; } - this.img = img; + this.width = img.width; + if (this.data) this.fire('data', {dataType: 'style'}); }); } @@ -59,15 +62,16 @@ class ImageSprite extends Evented { } loaded() { - return !!(this.data && this.img); + return !!(this.data && this.imgData); } resize(/*gl*/) { if (browser.devicePixelRatio > 1 !== this.retina) { const newSprite = new ImageSprite(this.base); newSprite.on('data', () => { - this.img = newSprite.img; this.data = newSprite.data; + this.imgData = newSprite.imgData; + this.width = newSprite.width; this.retina = newSprite.retina; }); } @@ -77,7 +81,7 @@ class ImageSprite extends Evented { if (!this.loaded()) return new SpritePosition(); const pos = this.data && this.data[name]; - if (pos && this.img) return pos; + if (pos && this.imgData) return pos; return new SpritePosition(); } diff --git a/js/symbol/sprite_atlas.js b/js/symbol/sprite_atlas.js index 468a689ba23..96b2b6abed1 100644 --- a/js/symbol/sprite_atlas.js +++ b/js/symbol/sprite_atlas.js @@ -109,8 +109,8 @@ class SpriteAtlas { } copy(dst, src, wrap) { - if (!this.sprite.img.data) return; - const srcImg = new Uint32Array(this.sprite.img.data.buffer); + if (!this.sprite.imgData) return; + const srcImg = new Uint32Array(this.sprite.imgData.buffer); this.allocate(); const dstImg = this.data; @@ -119,7 +119,7 @@ class SpriteAtlas { copyBitmap( /* source buffer */ srcImg, - /* source stride */ this.sprite.img.width, + /* source stride */ this.sprite.width, /* source x */ src.x, /* source y */ src.y, /* dest buffer */ dstImg, diff --git a/js/util/ajax.js b/js/util/ajax.js index fd259381142..cb7f6d75302 100644 --- a/js/util/ajax.js +++ b/js/util/ajax.js @@ -59,15 +59,7 @@ exports.getImage = function(url, callback) { return exports.getArrayBuffer(url, (err, imgData) => { if (err) return callback(err); const img = new window.Image(); - img.onload = () => { - const canvas = window.document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.width = img.width; - canvas.height = img.height; - context.drawImage(img, 0, 0); - img.data = context.getImageData(0, 0, img.width, img.height).data; - callback(null, img); - }; + img.onload = () => callback(null, img); if (!sameOrigin(url)) { img.crossOrigin = "Anonymous"; } diff --git a/js/util/browser.js b/js/util/browser.js index 7a621a36422..7a7721de811 100755 --- a/js/util/browser.js +++ b/js/util/browser.js @@ -64,6 +64,15 @@ exports.timed = function (fn, dur, ctx) { return function() { abort = true; }; }; +exports.getImageData = function (img) { + const canvas = window.document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.width = img.width; + canvas.height = img.height; + context.drawImage(img, 0, 0); + return context.getImageData(0, 0, img.width, img.height).data; +}; + /** * Test if the current browser supports Mapbox GL JS * @param {Object} options diff --git a/test/suite_implementation.js b/test/suite_implementation.js index 0dda6dab895..8da1e90c7f6 100644 --- a/test/suite_implementation.js +++ b/test/suite_implementation.js @@ -6,6 +6,7 @@ const request = require('request'); const PNG = require('pngjs').PNG; const Map = require('../js/ui/map'); const window = require('../js/util/window'); +const browser = require('../js/util/browser'); module.exports = function(style, options, _callback) { let wasCallbackCalled = false; @@ -100,15 +101,6 @@ function applyOperations(map, operations, callback) { } } -function fakeImage(png) { - return { - width: png.width, - height: png.height, - data: new Uint8Array(png.data), - complete: true - }; -} - const cache = {}; function cached(data, callback) { @@ -148,13 +140,13 @@ sinon.stub(ajax, 'getArrayBuffer', (url, callback) => { }); sinon.stub(ajax, 'getImage', (url, callback) => { - if (cache[url]) return cached(fakeImage(cache[url]), callback); + if (cache[url]) return cached(cache[url], callback); return request({url: url, encoding: null}, (error, response, body) => { if (!error && response.statusCode >= 200 && response.statusCode < 300) { new PNG().parse(body, (err, png) => { if (err) return callback(err); cache[url] = png; - callback(null, fakeImage(png)); + callback(null, png); }); } else { callback(error || new Error(response.statusCode)); @@ -162,6 +154,10 @@ sinon.stub(ajax, 'getImage', (url, callback) => { }); }); +sinon.stub(browser, 'getImageData', (img) => { + return new Uint8Array(img.data); +}); + // Hack: since node doesn't have any good video codec modules, just grab a png with // the first frame and fake the video API. sinon.stub(ajax, 'getVideo', (urls, callback) => {