pixelbutler
Version:
Low-res bitmap render engine for big screens
317 lines (316 loc) • 11.6 kB
JavaScript
'use strict';
var RGBA = require('./RGBA');
var microFont = require('../font/Micro');
var color = require('./color');
var util = require('./util');
var clamp = util.clamp;
var alpha = new RGBA(0, 0, 0, 0);
var black = new RGBA(0, 0, 0);
var magenta = new RGBA(255, 0, 255);
var Bitmap = (function () {
function Bitmap(width, height, useAlpha, buffer) {
if (typeof useAlpha === "undefined") { useAlpha = false; }
if (typeof buffer === "undefined") { buffer = null; }
this.width = width;
this.height = height;
this.useAlpha = useAlpha;
this.channels = (useAlpha ? 4 : 3);
if (buffer) {
var total = (this.width * this.height * this.channels);
if (buffer.byteLength !== total) {
throw new Error('bad raw data dimensions; expected ' + total + ', received ' + buffer.byteLength);
}
this.buffer = buffer;
this.data = new Uint8ClampedArray(this.buffer);
} else {
this._resetData();
}
}
Bitmap.prototype._resetData = function () {
this.buffer = new ArrayBuffer(this.width * this.height * this.channels);
this.data = new Uint8ClampedArray(this.buffer);
};
Bitmap.prototype.resizeTo = function (width, height) {
if (width === this.width && height === this.height) {
return;
}
this.width = width;
this.height = height;
this._resetData();
};
Bitmap.prototype.setPixel = function (x, y, col) {
x = Math.floor(x);
y = Math.floor(y);
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
return;
}
var p = (x + y * this.width) * this.channels;
this.data[p] = col.r;
this.data[p + 1] = col.g;
this.data[p + 2] = col.b;
};
Bitmap.prototype.getPixel = function (x, y, col) {
x = Math.floor(x);
y = Math.floor(y);
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
return null;
}
col = (col || new RGBA());
var p = (x + y * this.width) * this.channels;
col.r = this.data[p];
col.g = this.data[p + 1];
col.b = this.data[p + 2];
return col;
};
Bitmap.prototype.fillRect = function (x, y, width, height, col) {
x = Math.floor(x);
y = Math.floor(y);
width = Math.floor(width);
height = Math.floor(height);
if (x >= this.width || y >= this.height || x + width < 0 || y + height < 0) {
return;
}
var left = x;
var right = x + width;
var top = y;
var bottom = y + height;
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (right >= this.width) {
right = this.width;
}
if (bottom >= this.height) {
bottom = this.height;
}
for (var iy = top; iy < bottom; iy++) {
for (var ix = left; ix < right; ix++) {
var write = (ix + iy * this.width) * this.channels;
this.data[write] = col.r;
this.data[write + 1] = col.g;
this.data[write + 2] = col.b;
}
}
};
Bitmap.prototype.drawLineH = function (x, y, size, col) {
var right = clamp(Math.floor(x + size), 0, this.width);
x = clamp(Math.floor(x), 0, this.width);
y = clamp(Math.floor(y), 0, this.height);
for (; x < right; x++) {
var write = (x + y * this.width) * this.channels;
this.data[write] = col.r;
this.data[write + 1] = col.g;
this.data[write + 2] = col.b;
}
};
Bitmap.prototype.drawLineV = function (x, y, size, col) {
var bottom = clamp(Math.floor(y + size), 0, this.height);
x = clamp(Math.floor(x), 0, this.width);
y = clamp(Math.floor(y), 0, this.height);
for (; y < bottom; y++) {
var write = (x + y * this.width) * this.channels;
this.data[write] = col.r;
this.data[write + 1] = col.g;
this.data[write + 2] = col.b;
}
};
Bitmap.prototype.drawRect = function (x, y, width, height, col) {
x = Math.floor(x);
y = Math.floor(y);
width = Math.floor(width);
height = Math.floor(height);
this.drawLineH(x, y, width, col);
this.drawLineH(x, y + height - 1, width, col);
this.drawLineV(x, y, height, col);
this.drawLineV(x + width - 1, y, height, col);
};
Bitmap.prototype.fillCircle = function (x, y, r, col) {
x = Math.floor(x);
y = Math.floor(y);
r = Math.floor(r);
for (var iy = -r; iy <= r; iy++) {
for (var ix = -r; ix <= r; ix++) {
if (x + ix < 0 || y + iy < 0 || x + ix >= this.width || y + iy >= this.height) {
continue;
}
if (ix * ix + iy * iy <= r * r) {
var write = (x + ix + (y + iy) * this.width) * this.channels;
this.data[write] = col.r;
this.data[write + 1] = col.g;
this.data[write + 2] = col.b;
}
}
}
};
Bitmap.prototype.drawCircle = function (x, y, r, col) {
x = Math.floor(x);
y = Math.floor(y);
r = Math.floor(r);
for (var i = 0; i < 360; i++) {
var cx = Math.round(Math.cos(i * (Math.PI / 180)) * r) + x;
var cy = Math.round(Math.sin(i * (Math.PI / 180)) * r) + y;
if (cx < 0 || cy < 0 || cx >= this.width || cy >= this.height) {
continue;
}
var write = (cx + cy * this.width) * this.channels;
this.data[write] = col.r;
this.data[write + 1] = col.g;
this.data[write + 2] = col.b;
}
};
Bitmap.prototype.shader = function (f) {
var iy;
var ix;
var col;
var rgb = new RGBA();
for (iy = 0; iy < this.height; iy++) {
for (ix = 0; ix < this.width; ix++) {
var index = (ix + iy * this.width) * this.channels;
rgb.r = this.data[index];
rgb.g = this.data[index + 1];
rgb.b = this.data[index + 2];
col = f(ix, iy, rgb);
this.data[index] = col.r;
this.data[index + 1] = col.g;
this.data[index + 2] = col.b;
}
}
};
Bitmap.prototype.text = function (x, y, txt, col) {
txt = String(txt);
for (var i = 0; i < txt.length; i++) {
x += this.drawChar(x, y, txt.charAt(i), col) + 1;
}
};
Bitmap.prototype.drawChar = function (x, y, chr, col) {
var char = microFont.chars[chr.toUpperCase()];
if (!char) {
return 0;
}
for (var iy = 0; iy < microFont.height; iy++) {
for (var ix = 0; ix < char.width; ix++) {
if (char.map[iy * char.width + ix]) {
this.setPixel(x + ix, y + iy, col);
}
}
}
return char.width;
};
Bitmap.prototype.blit = function (sprite, x, y) {
x = (x ? Math.floor(x) : 0);
y = (y ? Math.floor(y) : 0);
var iy;
var ix;
var read;
var write;
if (x >= this.width || y >= this.height || x + sprite.width < 0 || y + sprite.height < 0) {
return;
}
var left = x;
var right = x + sprite.width;
var top = y;
var bottom = y + sprite.height;
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (right >= this.width) {
right = this.width;
}
if (bottom >= this.height) {
bottom = this.height;
}
if (sprite.useAlpha) {
for (iy = top; iy < bottom; iy++) {
for (ix = left; ix < right; ix++) {
read = (ix - x + (iy - y) * sprite.width) * sprite.channels;
write = (ix + iy * this.width) * this.channels;
var alpha = sprite.data[read + 3] / 255;
var inv = 1 - alpha;
this.data[write] = Math.round(this.data[write] * inv + sprite.data[read] * alpha);
this.data[write + 1] = Math.round(this.data[write + 1] * inv + sprite.data[read + 1] * alpha);
this.data[write + 2] = Math.round(this.data[write + 2] * inv + sprite.data[read + 2] * alpha);
}
}
} else {
for (iy = top; iy < bottom; iy++) {
for (ix = left; ix < right; ix++) {
read = (ix - x + (iy - y) * sprite.width) * sprite.channels;
write = (ix + iy * this.width) * this.channels;
this.data[write] = sprite.data[read];
this.data[write + 1] = sprite.data[read + 1];
this.data[write + 2] = sprite.data[read + 2];
}
}
}
};
Bitmap.prototype.clear = function (col) {
col = col || black;
var lim;
var i;
if (this.useAlpha && color.useAlpha(col)) {
lim = this.width * this.height * 4;
for (i = 0; i < lim; i += 4) {
this.data[i] = col.r;
this.data[i + 1] = col.g;
this.data[i + 2] = col.b;
this.data[i + 3] = col.a;
}
} else {
lim = this.width * this.height * this.channels;
for (i = 0; i < lim; i += this.channels) {
this.data[i] = col.r;
this.data[i + 1] = col.g;
this.data[i + 2] = col.b;
}
}
};
Bitmap.prototype.clearAlpha = function (alpha) {
if (typeof alpha === "undefined") { alpha = 0; }
if (!this.useAlpha) {
return;
}
var lim = this.width * this.height * 4;
for (var i = 3; i < lim; i += 4) {
this.data[i] = alpha;
}
};
Bitmap.clipFromData = function (inputData, inputWidth, inputHeight, inputChannels, x, y, width, height, useAlpha) {
var channels = useAlpha ? 4 : 3;
var data = new Uint8Array(height * width * channels);
var iy;
var ix;
var read;
var write;
if (useAlpha) {
for (iy = 0; iy < height; iy++) {
for (ix = 0; ix < width; ix++) {
read = (ix + x + (iy + y) * inputWidth) * inputChannels;
write = (ix + iy * width) * channels;
data[write] = inputData[read];
data[write + 1] = inputData[read + 1];
data[write + 2] = inputData[read + 2];
data[write + 3] = inputData[read + 3];
}
}
} else {
for (iy = 0; iy < height; iy++) {
for (ix = 0; ix < width; ix++) {
read = (ix + x + (iy + y) * inputWidth) * inputChannels;
write = (ix + iy * width) * channels;
data[write] = inputData[read];
data[write + 1] = inputData[read + 1];
data[write + 2] = inputData[read + 2];
}
}
}
return new Bitmap(width, height, useAlpha, data);
};
return Bitmap;
})();
module.exports = Bitmap;