Initial commit

This commit is contained in:
Nathan Kunicki 2017-11-17 23:53:24 +00:00
commit 6f0ac057be
8 changed files with 297 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/*
static/js/*

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>BittMapp</title>
<script src="./static/js/bittmappeditor.js"></script>
<script src="./static/js/main.js"></script>
<link rel="stylesheet" href="./static/css/styles.css" />
</head>
<body>
<canvas id="editor"></canvas>
</body>
</html>

14
package-lock.json generated Normal file
View File

@ -0,0 +1,14 @@
{
"name": "bittmapp",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"typescript": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.1.tgz",
"integrity": "sha1-7znN6ierrAtQAkLWcmq5DgyEZjE=",
"dev": true
}
}
}

13
package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "bittmapp",
"version": "0.0.1",
"description": "BittMapp - A bitmap image editor",
"scripts": {
"build": "tsc"
},
"author": "Nathan Kunicki <me@nathankunicki.com>",
"license": "MIT",
"devDependencies": {
"typescript": "^2.6.1"
}
}

228
src/bittmappeditor.ts Normal file
View File

@ -0,0 +1,228 @@
interface BittMappEditorConstructorOptions {
canvas: HTMLCanvasElement,
canvasWidth: number,
canvasHeight: number,
width: number,
height: number
}
class BittMappEditor {
public canvasWidth: number;
public canvasHeight: number;
private _canvas: HTMLCanvasElement;
private _context: CanvasRenderingContext2D;
private _width: number;
private _height: number;
private _scale: number;
private _deviceRatio: number;
private _pixelWidth: number = 0;
private _pixelHeight: number = 0;
private _mouseDown: boolean = false;
private _mouseButton: number;
private _data: Uint8Array;
constructor (options: BittMappEditorConstructorOptions) {
options = options || {};
if (options.canvas) {
this._canvas = options.canvas;
} else {
throw new Error("BittMappEditor must be initialized with a canvas.");
}
if (options.canvasWidth) {
this.canvasWidth = options.canvasWidth;
} else {
throw new Error("BittMappEditor must be constructed with a canvasWidth.");
}
if (options.canvasHeight) {
this.canvasHeight = options.canvasHeight;
} else {
throw new Error("BittMappEditor must be constructed with a canvasHeight.");
}
if (options.width) {
this._width = options.width;
} else {
throw new Error("BittMappEditor must be constructed with a width.");
}
if (options.height) {
this._height = options.height;
} else {
throw new Error("BittMappEditor must be constructed with a height.");
}
this._context = <CanvasRenderingContext2D> this._canvas.getContext("2d");
const deviceRatio: number = window.devicePixelRatio,
backingStoreRatio: number = <number> (this._context as any).backingStorePixelRatio || 1;
this._scale = deviceRatio / backingStoreRatio;
this._deviceRatio = deviceRatio;
this._canvas.width = this.canvasWidth * this._scale;
this._canvas.height = this.canvasHeight * this._scale;
this._canvas.style.width = `${this.canvasWidth}px`;
this._canvas.style.height = `${this.canvasHeight}px`;
this._context.setTransform(this._scale, 0, 0, this._scale, 0, 0);
this.resize();
this._canvas.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
this._canvas.addEventListener("mousedown", (event) => {
this._mouseDown = true;
this._mouseButton = event.button;
this._handleMouseEvent(event, this._mouseButton);
});
this._canvas.addEventListener("mousemove", (event) => {
if (this._mouseDown) {
this._handleMouseEvent(event, this._mouseButton);
}
});
this._canvas.addEventListener("mouseup", (event) => {
this._mouseDown = false;
});
}
setPixel (x: number, y: number) {
const byte: number = ((y * 4) + Math.floor(x / 8)),
mask: number = 1 << (x % 8);
this._data[byte] = this._data[byte] |= mask;
}
unsetPixel (x: number, y: number) {
const byte: number = ((y * 4) + Math.floor(x / 8)),
mask: number = 1 << (x % 8);
this._data[byte] = this._data[byte] &= ~mask;
}
resize (width: number = this._width, height: number = this._height) {
this._data = new Uint8Array((width / 8) * height);
this._pixelWidth = this.canvasWidth / this._width;
this._pixelHeight = this.canvasHeight / this._height;
this._redraw();
}
set height (height: number) {
this.resize(this._width, height);
}
set width (width: number) {
this.resize(width, this._height);
}
_handleMouseEvent (event: MouseEvent, button: number) {
const pixelX: number = Math.floor(event.offsetX / this._pixelWidth),
pixelY: number = Math.floor(event.offsetY / this._pixelHeight);
if (button === 0) {
this.setPixel(pixelX, pixelY);
} else if (button === 2) {
this.unsetPixel(pixelX, pixelY);
}
this._redraw();
}
_redraw () {
this._drawGrid();
this._drawPixels();
}
_drawGrid () {
this._context.fillStyle = "#FFFFFF";
this._context.strokeStyle = "#FF0000";
this._context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
this._context.strokeStyle = "#FF0000";
this._context.beginPath();
this._context.moveTo(0, 0);
this._context.lineTo(this.canvasWidth, 0);
this._context.stroke();
this._context.beginPath();
this._context.moveTo(0, 0);
this._context.lineTo(0, this.canvasHeight);
this._context.stroke();
for (let i: number = 0; i < (this._width); i++) {
const x: number = Math.floor((this.canvasWidth / this._width) * (i + 1));
this._context.beginPath();
this._context.moveTo(x, 0);
this._context.lineTo(x, this.canvasHeight);
this._context.stroke();
}
for (let j: number = 0; j < (this._height); j++) {
const y: number = Math.floor((this.canvasHeight / this._height) * (j + 1));
this._context.beginPath();
this._context.moveTo(0, y);
this._context.lineTo(this.canvasWidth, y);
this._context.stroke();
}
}
_drawPixels () {
this._context.fillStyle = "#000000";
this._context.strokeStyle = "#FFFFFFF";
for (let i: number = 0; i < this._height; i++) {
for (let j: number = 0; j < (this._width / 8); j++) {
let byte: number = this._data[(i * 4) + j];
for (let k: number = 0; k < 8; k++) {
if (byte & 1) {
this._context.fillRect(0.5 + (((j * 8) + k) * this._pixelWidth), 0.5 + (i * this._pixelHeight), this._pixelWidth - 1, this._pixelHeight - 1);
}
byte = byte >> 1;
}
}
}
}
}

11
src/main.ts Normal file
View File

@ -0,0 +1,11 @@
window.onload = function () {
let editor: BittMappEditor = new BittMappEditor({
canvas: <HTMLCanvasElement> document.getElementById("editor"),
canvasWidth: 640,
canvasHeight: 640,
width: 32,
height: 32
});
};

3
static/css/styles.css Normal file
View File

@ -0,0 +1,3 @@
body {
background-color: #666666;
}

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es6",
"module": "none",
"strict": true,
"outDir": "static/js"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}