summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzlg <zlg@zlg.space>2024-02-29 23:07:52 -0800
committerzlg <zlg@zlg.space>2024-02-29 23:07:52 -0800
commit7ce4d6f86f6b2233feb7cf332b0e236c76b7ff53 (patch)
treed9c0136f0708dbae347fb2943e56011529b5df9e
parentRight click toggles to another tool as needed (diff)
downloadpixeled-js-master.tar.gz
pixeled-js-master.tar.bz2
pixeled-js-master.tar.xz
pixeled-js-master.zip
Giant refactor to modern-ish interfaceHEADmaster
Completely changes the interface to something more akin to a modern image editor, with a palette of icons for tools. New icons are in place, with a tool bar that stays inside the viewport at all times. Additionally, drawing behavior more closely matches that of The GIMP, et al in how it handles clicks. Due to how new I am to Javascript and the relative complexity of implementing a pixel editor, I may not offer a mobile version at this time.
-rw-r--r--pixeled.css177
-rw-r--r--pixeled.js318
-rw-r--r--ui/black-pen.pngbin0 -> 626 bytes
-rw-r--r--ui/blank-canvas.pngbin0 -> 645 bytes
-rw-r--r--ui/clear-screen.pngbin0 -> 760 bytes
-rw-r--r--ui/custom-color-mask.pngbin0 -> 1163 bytes
-rw-r--r--ui/custom-color.pngbin0 -> 773 bytes
-rw-r--r--ui/cyan-button-pressed.pngbin0 -> 654 bytes
-rw-r--r--ui/cyan-button.pngbin0 -> 7164 bytes
-rw-r--r--ui/darken-tool.pngbin0 -> 608 bytes
-rw-r--r--ui/eraser.pngbin0 -> 757 bytes
-rw-r--r--ui/green-button-pressed.pngbin0 -> 654 bytes
-rw-r--r--ui/green-button.pngbin0 -> 6899 bytes
-rw-r--r--ui/grid-toggle.pngbin0 -> 565 bytes
-rw-r--r--ui/lighten-tool.pngbin0 -> 625 bytes
-rw-r--r--ui/rainbow-pen.pngbin0 -> 694 bytes
-rw-r--r--ui/save-file.pngbin0 -> 797 bytes
-rw-r--r--ui/white-pen.pngbin0 -> 635 bytes
-rw-r--r--ui/yellow-button-pressed.pngbin0 -> 657 bytes
-rw-r--r--ui/yellow-button.pngbin0 -> 7348 bytes
20 files changed, 387 insertions, 108 deletions
diff --git a/pixeled.css b/pixeled.css
index 5f02f19..1cef83a 100644
--- a/pixeled.css
+++ b/pixeled.css
@@ -2,36 +2,26 @@ html, body {
margin: 0;
padding: 0;
user-select: none;
- font-size: 1vmin;
+ font-size: 10px;
color: #fff;
}
-button {
- border: 0;
- font-size: inherit;
- font-family: inherit;
- color: inherit;
- font-size: 3rem;
- margin-collapse: collapse;
- text-shadow: 0 0 3px #000;
+body {
+ overflow: hidden;
}
#app_area {
display: grid;
- grid-template-areas:
- "bar editor";
- grid-template-columns: minmax(50rem,30vw) auto;
height: 100vh;
width: 100vw;
}
/* The area behind the grid */
#workspace {
- background: #1e3526 linear-gradient(to right, #000000 -7%, transparent 8%);
- grid-area: editor;
+ background: #1e3526 linear-gradient(to right, #000000 -7%, transparent 8%, transparent 92%, #000000 107%);
display: grid;
overflow: scroll;
- grid-template-rows: calc(100% - 7rem) 1fr;
+ z-index: 1;
}
/* the canvas we're drawing on in the app */
@@ -43,7 +33,7 @@ button {
background: #888;
aspect-ratio: 1;
box-shadow: 0 0 3.2rem #000;
- z-index: 1;
+ z-index: 10;
height: 75vmin;
align-self: center;
justify-self: center;
@@ -61,93 +51,164 @@ button {
border-collapse: collapse;
}
-#grid_caption {
- align-self: center;
- justify-self: center;
- font-size: 4rem;
-}
-
#toolbox {
- grid-area: bar;
- background: #00762a;
- border-right: 3px solid #0b3a1d;
+ background: #00000088;
+ border: 1px solid #000;
+ border-radius: 8px;
display: grid;
box-sizing: border-box;
- padding: 0 1em 1em 1em;
- font-size: 3rem;
+ box-shadow: 0 0 16px #000;
+ padding: 6px;
+ font-size: 14px;
font-family: "PT Mono", "Envy Code R", "Liberation Mono", "DejaVu Sans", monospace;
font-weight: bold;
- z-index: 2;
- gap: 2rem;
- grid-template-rows: 10vh 15vh 20vh 1fr 1fr 1fr 1fr 1fr;
+ position: absolute;
+ z-index: 20;
+ gap: 8px;
+ grid-template-rows: 26px 1fr 60px;
+ top: 16px;
+ left: 16px;
+ width: 184px;
}
#toolbox > header {
margin: 0;
padding: 0;
- font-size: 3em;
+ font-size: 1.3em;
text-align: center;
text-shadow: 0 0 3px #000;
- align-self: center;
- justify-self: center;
+ line-height: 26px;
}
-#color_picker {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
+#tool_palette {
+ display: flex;
+ flex-flow: row wrap;
+ gap: 3px;
}
-#color_picker > div {
+#tool_palette div {
+ background: url("./ui/green-button.png") no-repeat top left transparent;
display: grid;
- grid-template-rows: 70% 30%;
text-align: center;
font-weight: bold;
font-size: 1.3em;
+ image-rendering: crisp-edges;
+ flex-basis: 40px;
+ height: 48px;
+}
+
+#tool_palette div:hover {
+ background-image: url("./ui/cyan-button.png");
+}
+
+#tool_palette > div.selected {
+ background: url("./ui/green-button-pressed.png") no-repeat top left;
+ image-rendering: crisp-edges;
+}
+
+#tool_palette > div.active {
+ background: url("./ui/yellow-button-pressed.png") no-repeat top left;
+ image-rendering: crisp-edges;
}
-#color_picker > div.selected {
- background: rgba(255,255,255, 0.4);
+#tool_palette div.selected .swatch,
+#tool_palette div.active .swatch {
+ margin-top: 9px;
}
-#color_picker > div > .swatch {
+#tool_palette > div > .swatch {
aspect-ratio: 1;
- margin: 0.4em auto 0.2em auto;
+ margin: 4px auto 0 auto;
display: block;
- height: 4rem;
+ width: 32px;
+ height: 32px;
}
#black_pick > .swatch {
- background: #000;
+ background: url("./ui/black-pen.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
}
#white_pick > .swatch {
- background: #fff;
+ background: url("./ui/eraser.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
}
#custom_pick > .swatch {
+ mask: url("./ui/custom-color-mask.png") no-repeat center center;
+ mask-size: contain;
+ border: 0;
+ padding: 0;
}
-#canvas_opts {
- display: grid;
- grid-template-rows: 2.5rem 1fr 1fr;
+#custom_color::-moz-color-swatch {
+ background: url("./ui/custom-color.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+#rainbow > .swatch {
+ background: url("./ui/rainbow-pen.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
+}
+
+#darken > .swatch {
+ background: url("./ui/darken-tool.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
+}
+
+#lighten > .swatch {
+ background: url("./ui/lighten-tool.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
+}
+
+#clear_canvas > .swatch {
+ background: url("./ui/blank-canvas.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
+}
+
+#grid_toggle > .swatch {
+ background: url("./ui/grid-toggle.png") no-repeat center center;
+ background-size: contain;
+ image-rendering: crisp-edges;
}
-#rainbow {
- background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet);
- border: 2px solid rgba(0,0,0,0.4);
+#canvas_opts {
+ display: grid;
+ grid-template-areas:
+ "lbl lbl"
+ "num slider"
+ ;
+ grid-template-columns: 2.5em auto;
+ gap: 0 4px;
}
-#darken {
- background: #0b3a1d;
+#canvas_opts > label {
+ grid-area: lbl;
}
-#lighten {
- background: #20964a;
+#canvas_opts input[type=range] {
+ grid-area: slider;
+ width: 100%;
}
-button.selected {
- border: 4px solid #fff !important;
+#canvas_opts input[type=text] {
+ grid-area: num;
+ text-align: center;
+ background: #fff8;
+ border: 0;
+ border-radius: 5px;
}
-#btn_overlay {
+#canvas_opts input[type=text]:focus {
+ border: 1px solid #fff;
}
diff --git a/pixeled.js b/pixeled.js
index fd6a890..8bf3a94 100644
--- a/pixeled.js
+++ b/pixeled.js
@@ -30,10 +30,39 @@
* [*] click-and-drag to draw, not hover
* [*] right click to copy color under cursor (and switch to it)
*
- * STRETCH GOAL
+ * STRETCH GOALS
*
- * [ ] better UI layout
- * [ ] possible export to PNG or GIF format (!!! :D)
+ * [*] better UI layout
+ * [ ] possible export to GIF format (!!! :D)
+ */
+
+/* POST-COMPLETION
+ *
+ * This project has helped me understand what goes into making modern Web apps,
+ * and the value that some frameworks can bring when you're building something
+ * with somewhat complex UI language, like an editing tool. There is a lot of
+ * event and state syncing, for lack of a better explanation, which increases
+ * the complexity of the task you're carrying out. Web apps can be convenient
+ * to visit and use, but building them is a challenge. I think I would be more
+ * at home in a persistent ticking environment like a video game, or somewhat
+ * predictable backend code. The moving target of HTML5 and CSS3 and JS, while
+ * fun in some respects, adds to the ever-increasing complexity that developers
+ * face when building for the Web.
+ *
+ * I would prefer to build on a platform that is less prone to change. There's
+ * bound to be a middle ground between low-level tedium and a gargantuan
+ * virtual machine.
+ */
+
+/* CREDITS
+ *
+ * Wikipedia for the GIF information
+ * MDN for endless Web and Javascript info
+ * The Command Line Fanatic, who wrote two great articles on GIF:
+ * https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art010
+ * https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art011
+ *
+ * Implementing GIF would have been more of a hassle without these resources.
*/
/* disable right click's default behavior */
@@ -56,6 +85,7 @@ current_color = "#000000";
custom_color = "#2564b8";
draw_state = dstates["BLACK"];
do_draw = false;
+do_move = false;
function clean_element(node) {
while (node.firstChild) {
@@ -79,7 +109,6 @@ function rebuildCanvas(size) {
}
});
box.addEventListener("mousedown", function(e) {
- // console.log(e.target.style.backgroundColor);
if (e.which == 3 || e.button == 2) {
if (e.target.style.backgroundColor == 'rgb(0, 0, 0)') {
setBlack();
@@ -92,21 +121,23 @@ function rebuildCanvas(size) {
setCustom();
}
} else {
- do_draw = true;
+ // do_draw = true;
drawColor(e.target, current_color);
}
}, { "passive": false, "capture": true } );
- box.addEventListener("mouseup", function(e) {
- do_draw = false;
- }, { "passive": false } );
+ // box.addEventListener("mouseup", function(e) {
+ // do_draw = false;
+ // }, { "passive": false } );
}
}
function clearCanvas() {
- pixels = document.querySelectorAll("#grid_container div");
- pixels.forEach(function(i) {
- i.removeAttribute("style");
- });
+ if (window.confirm("Clear the entire canvas?")) {
+ pixels = document.querySelectorAll("#grid_container div");
+ pixels.forEach(function(i) {
+ i.removeAttribute("style");
+ });
+ }
}
function handler() {
@@ -122,15 +153,11 @@ function initialSetup() {
grid.id = ["grid_container"];
grid.classList = ["show_grid"];
/* turn the pen off when you leave the canvas */
- grid.addEventListener("mouseleave",
- (e) => {
- do_draw = false;
- }, { "passive": false }
- );
-
- grid_caption = document.createElement("div");
- grid_caption.id = ["grid_caption"];
- grid_caption.textContent = 'an app by ZLG';
+ // grid.addEventListener("mouseleave",
+ // (e) => {
+ // do_draw = false;
+ // }, { "passive": false }
+ // );
resetCanvas(16);
/* we should have 256 divs appended to the grid. */
@@ -141,18 +168,30 @@ function initialSetup() {
* the same thing in pure JS. It's all scaffolding anyway. */
toolbox.innerHTML = `
<header>pixelED</header>
- <section id="color_picker">
+ <section id="tool_palette">
<div id="black_pick">
<span class="swatch"></span>
- <span class="sw_label">black</span>
</div>
<div id="white_pick">
<span class="swatch"></span>
- <span class="sw_label">white</span>
</div>
<div id="custom_pick">
<input class="swatch" type="color" value="" name="custom_color" id="custom_color"></input>
- <span class="sw_label">custom</span>
+ </div>
+ <div id="rainbow">
+ <span class="swatch"></span>
+ </div>
+ <div id="darken">
+ <span class="swatch"></span>
+ </div>
+ <div id="lighten">
+ <span class="swatch"></span>
+ </div>
+ <div id="clear_canvas">
+ <span class="swatch"></span>
+ </div>
+ <div id="grid_toggle" class="active">
+ <span class="swatch"></span>
</div>
</section>
<section id="canvas_opts">
@@ -160,35 +199,46 @@ function initialSetup() {
<input type="range" min="8" max="100" value="16" name="canvas_size" id="canvas_size" list="sizes"></input>
<input type="text" value="16"></input>
</section>
- <button id="rainbow">rainbow</button>
- <button id="darken">darken</button>
- <button id="lighten">lighten</button>
- <button id="erase">erase</button>
- <button id="grid_toggle">grid on</button>
- <datalist id="sizes">
- <option value="16"></option>
- <option value="32"></option>
- <option value="48"></option>
- <option value="64"></option>
- <option value="80"></option>
- </datalist>
`;
/* elements must be added to the page in order to be queried */
root.appendChild(toolbox);
root.appendChild(workspace);
workspace.appendChild(grid);
- workspace.appendChild(grid_caption);
+
+ /* the area behind the grid */
+ workspace.addEventListener("mousedown", (e) => {
+ do_draw = true;
+ });
+ /* the area behind the grid */
+ workspace.addEventListener("mouseup", (e) => {
+ do_draw = false;
+ });
/* initial settings */
setBlack();
document.querySelector("#custom_color").value = custom_color;
+ /* click-and-drag for the toolbox */
+ document.querySelector("#toolbox header").addEventListener("mousedown", (e) => {
+ e.preventDefault();
+ x1 = e.clientX;
+ y1 = e.clientY;
+ xoff = e.clientX - e.target.parentNode.offsetLeft;
+ yoff = e.clientY - e.target.parentNode.offsetTop;
+ do_move = true;
+ document.addEventListener("mousemove", moveToolbox);
+ document.addEventListener("mouseup", (e) => {
+ e.preventDefault();
+ do_move = false;
+ }, { once: true });
+ });
+
/* add events, AFTER added to DOM above */
document.getElementById("rainbow").addEventListener("click", setRainbow);
document.getElementById("darken").addEventListener("click", setDarken);
document.getElementById("lighten").addEventListener("click", setLighten);
- document.getElementById("erase").addEventListener("click", clearCanvas);
+ document.getElementById("clear_canvas").addEventListener("click", clearCanvas);
/* The canvas slider */
document.querySelector("#canvas_size").addEventListener(
@@ -253,7 +303,6 @@ function initialSetup() {
/* The Custom color option */
document.querySelector('#custom_color').addEventListener("input", setCustom);
- document.querySelector('#custom_pick > span').addEventListener("click", setCustom);
document.querySelector('#custom_pick').addEventListener("click", setCustom);
/* The Toggle Grid option */
@@ -304,11 +353,7 @@ function setLighten() {
function toggleGrid(e) {
document.querySelector("#grid_container").classList.toggle("show_grid");
- if (document.querySelector("#grid_container").classList.contains("show_grid")) {
- e.target.textContent = "grid on";
- } else {
- e.target.textContent = "grid off";
- }
+ document.querySelector("#grid_toggle").classList.toggle("active");
}
function lintCanvasSize(s) {
@@ -329,11 +374,22 @@ function clearSelected() {
);
}
-function getRGBTriplet(str) {
- if (str[0] == "#") {
- return hex_to_rgb(str);
+function getRGBTriplet(s) {
+ console.log(s);
+ if (s.length == 0) {
+ return [255, 255, 255];
+ }
+ if (s[0] == "#" || s.length == 6) {
+ return hex_to_rgb(s);
}
- return str.match(new RegExp("rgb\\((.*)\\)"))[1].split(", ");
+ triplet = s.match(new RegExp("rgb\\((.*)\\)"))[1].split(", ");
+ nums = new Array();
+ triplet.forEach(
+ (c) => {
+ nums.push(Number.parseInt(c));
+ }
+ );
+ return nums;
}
/* transform a hex color into rgb(x, y, z) format */
@@ -346,7 +402,7 @@ function hex_to_rgb(str) {
*/
if (str[0] == "#") {
str = str.substr(1);
- console.log(str);
+ // console.log(str);
}
switch (str.length) {
case 3:
@@ -447,3 +503,165 @@ function gen_lighten(c) {
let is = nc_array.join(", ");
return `rgb(${is})`;
}
+
+function moveToolbox(e) {
+ if (!do_move) {
+ return 0;
+ }
+ e = e || window.event;
+ e.preventDefault();
+ let tb = document.querySelector("#toolbox");
+ // console.log(`toolbar at ${tb.offsetLeft},${tb.offsetTop}`);
+ tb.style.left = `${e.clientX - xoff}px`;
+ tb.style.top = `${(e.clientY - yoff)}px`;
+ boundToolbox();
+}
+
+function myMid(lowb, num, highb) {
+ return Math.min(Math.max(lowb, num), highb);
+}
+
+function boundToolbox() {
+ let tb = document.querySelector("#toolbox");
+ let tbh = document.querySelector("#toolbox header");
+ let gap = tbh.style.gap;
+ tb.style.left = `${myMid(0,tb.offsetLeft,(window.innerWidth - tb.offsetWidth))}px`;
+ tb.style.top = `${myMid(0,tb.offsetTop,(window.innerHeight - (tbh.offsetHeight + 16)))}px`;
+}
+
+/* Implement a simple GIF class to facilitate easy downloading of user's work
+ *
+ * Limitations include:
+ * * no animation
+ * * no sub-images for complex palettes
+ * * no transparency
+ * * no palette correction should the colorspace run out
+ *
+ * In short, please only use this as a way to get pixelED art into another
+ * program.
+ */
+class GIFImage {
+ #header = "GIF89a";
+ #width;
+ #height;
+ #pixel_data;
+ #pixel_view;
+ raw_binary;
+ theGCT;
+ theIDS;
+
+ constructor(height, width, pdata) {
+ this.#width = width;
+ this.#height = height;
+ this.#pixel_data = new Uint8Array(3*(this.#width*this.#height));
+ this.#pixel_view = new DataView(this.#pixel_data.buffer, 0);
+ this.fill_buffer(pdata);
+ this.build_gct();
+ }
+
+ // fill the data buffer with background color information from this element's children.
+ fill_buffer(grid_area) {
+ if (grid_area.children.length != (this.#pixel_data.length / 3) ) {
+ console.log("pixelED Error: Size mismatch between canvas and GIF buffer size!");
+ return;
+ } else {
+ // every pixel has three values, thus we can know offsets
+ for (i=0; i<this.#pixel_data.length; i+=3) {
+ let grid_index = i / 3;
+ let pixel = getRGBTriplet(grid_area.children[grid_index].style.backgroundColor);
+ this.#pixel_view.setUint8(i,pixel[0]);
+ this.#pixel_view.setUint8(i+1,pixel[1]);
+ this.#pixel_view.setUint8(i+2,pixel[2]);
+ }
+ }
+ }
+
+ // make the global color table, and add color indices to the image data section
+ build_gct() {
+ // setup the gct naively
+ this.theGCT = new Array();
+ this.theIDS = new Array();
+ grid.childNodes.forEach(
+ (p) => {
+ let hex = rgb_to_hex(p.style.backgroundColor).substring(1);
+ if (! this.theGCT.includes(hex)) {
+ this.theGCT.push(hex);
+ }
+ let ci = this.theGCT.indexOf(hex);
+ this.theIDS.push(ci);
+ }
+ );
+ }
+
+ gct_to_binary() {
+ let buf = new Uint8Array(128*3);
+ for (i=0; (i/3) <= this.theGCT.length; i+=3) {
+ let rgb = getRGBTriplet(this.theGCT[i/3]);
+ buf[i] = rgb[0];
+ buf[i+1] = rgb[1];
+ buf[i+2] = rgb[2];
+ }
+ return new DataView(buf.buffer, 0);
+ }
+
+ ids_to_binary() {
+ let buf = new Uint8Array(this.theIDS.length);
+ for (i=0; i < buf.byteLength; i++) {
+ buf[i] = this.theIDS[i];
+ }
+ return new DataView(buf.buffer, 0);
+ }
+
+ // don't hurt me i'm still a babby
+ writeData() {
+ // we need to figure out the length of our information before we allocate a buffer.
+ let size = 0;
+ size += 6; // header: 'GIF89a'
+ size += 2; // logical screen width
+ size += 2; // logical screen height
+ size += 1; // GCT (Global Color Table) information
+ // -- u are here
+ size += 1; // background color index
+ size += 1; // pixel aspect ratio
+ size += this.gct_to_binary().byteLength;
+ // we'll try omitting the GCE
+ size += 1; // image descriptor ','
+ size += 4; // upper-left coord of image in logical screen
+ size += 4; // image width and height
+ size += 1; // local color table bit
+ size += 1; // minimum LZW code size - 1 (7 for 8-bit indices)
+ size += 1; // CLEAR code (80)
+ size += this.ids_to_binary().byteLength;
+ size += 1; // STOP code (81)
+ size += 1; // last ';' in the file
+ console.log("we need "+size+" bytes");
+
+ // do the business
+ this.raw_binary = new Uint8Array(size);
+ let offset = 0;
+ // write header
+ for (i=0; i<this.#header.length; i++) {
+ this.raw_binary[i] = this.#header[i].charCodeAt(0);
+ offset++;
+ }
+ // write LS width and height
+ this.raw_binary[offset] = (this.#width & 255);
+ offset++;
+ this.raw_binary[offset] = (this.#width) >> 8;
+ offset++;
+ this.raw_binary[offset] = (this.#height & 255);
+ offset++;
+ this.raw_binary[offset] = (this.#height) >> 8;
+ offset++;
+
+ // write BG index and aspect ratio
+ this.raw_binary[offset++] = 0;
+ this.raw_binary[offset++] = 0;
+
+ let packed_field = 0b10110110;
+
+ console.log(offset);
+ }
+}
+
+/* vim: set foldmethod=syntax: */
diff --git a/ui/black-pen.png b/ui/black-pen.png
new file mode 100644
index 0000000..3d6af14
--- /dev/null
+++ b/ui/black-pen.png
Binary files differ
diff --git a/ui/blank-canvas.png b/ui/blank-canvas.png
new file mode 100644
index 0000000..b086ca7
--- /dev/null
+++ b/ui/blank-canvas.png
Binary files differ
diff --git a/ui/clear-screen.png b/ui/clear-screen.png
new file mode 100644
index 0000000..b88c30b
--- /dev/null
+++ b/ui/clear-screen.png
Binary files differ
diff --git a/ui/custom-color-mask.png b/ui/custom-color-mask.png
new file mode 100644
index 0000000..7eebcb3
--- /dev/null
+++ b/ui/custom-color-mask.png
Binary files differ
diff --git a/ui/custom-color.png b/ui/custom-color.png
new file mode 100644
index 0000000..c35ee4c
--- /dev/null
+++ b/ui/custom-color.png
Binary files differ
diff --git a/ui/cyan-button-pressed.png b/ui/cyan-button-pressed.png
new file mode 100644
index 0000000..13af424
--- /dev/null
+++ b/ui/cyan-button-pressed.png
Binary files differ
diff --git a/ui/cyan-button.png b/ui/cyan-button.png
new file mode 100644
index 0000000..4fe6b62
--- /dev/null
+++ b/ui/cyan-button.png
Binary files differ
diff --git a/ui/darken-tool.png b/ui/darken-tool.png
new file mode 100644
index 0000000..2bc005c
--- /dev/null
+++ b/ui/darken-tool.png
Binary files differ
diff --git a/ui/eraser.png b/ui/eraser.png
new file mode 100644
index 0000000..d4d01b0
--- /dev/null
+++ b/ui/eraser.png
Binary files differ
diff --git a/ui/green-button-pressed.png b/ui/green-button-pressed.png
new file mode 100644
index 0000000..054ab8d
--- /dev/null
+++ b/ui/green-button-pressed.png
Binary files differ
diff --git a/ui/green-button.png b/ui/green-button.png
new file mode 100644
index 0000000..d37ce9a
--- /dev/null
+++ b/ui/green-button.png
Binary files differ
diff --git a/ui/grid-toggle.png b/ui/grid-toggle.png
new file mode 100644
index 0000000..1844aac
--- /dev/null
+++ b/ui/grid-toggle.png
Binary files differ
diff --git a/ui/lighten-tool.png b/ui/lighten-tool.png
new file mode 100644
index 0000000..4a65072
--- /dev/null
+++ b/ui/lighten-tool.png
Binary files differ
diff --git a/ui/rainbow-pen.png b/ui/rainbow-pen.png
new file mode 100644
index 0000000..26ce0b8
--- /dev/null
+++ b/ui/rainbow-pen.png
Binary files differ
diff --git a/ui/save-file.png b/ui/save-file.png
new file mode 100644
index 0000000..2df1a01
--- /dev/null
+++ b/ui/save-file.png
Binary files differ
diff --git a/ui/white-pen.png b/ui/white-pen.png
new file mode 100644
index 0000000..ef36c29
--- /dev/null
+++ b/ui/white-pen.png
Binary files differ
diff --git a/ui/yellow-button-pressed.png b/ui/yellow-button-pressed.png
new file mode 100644
index 0000000..fe637d2
--- /dev/null
+++ b/ui/yellow-button-pressed.png
Binary files differ
diff --git a/ui/yellow-button.png b/ui/yellow-button.png
new file mode 100644
index 0000000..bb34269
--- /dev/null
+++ b/ui/yellow-button.png
Binary files differ