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