Initial commit
This commit is contained in:
commit
6f0ac057be
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/*
|
||||
static/js/*
|
12
index.html
Normal file
12
index.html
Normal 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
14
package-lock.json
generated
Normal 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
13
package.json
Normal 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
228
src/bittmappeditor.ts
Normal 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
11
src/main.ts
Normal 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
3
static/css/styles.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
background-color: #666666;
|
||||
}
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "none",
|
||||
"strict": true,
|
||||
"outDir": "static/js"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user