Add various code comments

main
kts of kettek 2024-04-08 16:01:16 -07:00
parent f453615478
commit ba12fef1c6
20 changed files with 141 additions and 1 deletions

View File

@ -1,3 +1,8 @@
<!--
@component
This component is a modal settings dialog for changing the background color.
-->
<script lang='ts'>
import { Column, Grid, Modal, NumberInput, Row, TextInput } from "carbon-components-svelte";

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a visualization of the current brush size and shape and controls for changing them.
-->
<script lang='ts'>
import { OverflowMenu, OverflowMenuItem } from "carbon-components-svelte";
import { FilledCircle, FilledSquare, type PixelPosition } from "../types/shapes"

View File

@ -1,3 +1,8 @@
<!--
@component
This component is a modal that provides settings for changing the checkerboard background.
-->
<script lang='ts'>
import { Column, Grid, Modal, NumberInput, Row, TextInput } from "carbon-components-svelte";

View File

@ -1,3 +1,8 @@
<!--
@component
This component shows a given color swatch/index and provides controls for adding and replacing swatches within the palette.
-->
<script lang='ts'>
import { Button } from "carbon-components-svelte";
import { AddLarge, ColorSwitch } from "carbon-icons-svelte";

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides HSV controls for adjusting RGBA input values.
-->
<script lang='ts'>
import { HSV2RGB, RGB2HSV } from "../types/colors"

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a modal for deleting a swatch from the palette.
-->
<script lang='ts'>
import { Checkbox, Column, Dropdown, Grid, Modal, NumberInput, Row, TextInput } from "carbon-components-svelte";
import type { LoadedFile } from "../types/file"

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a draggable floating panel that contains arbitrary content.
-->
<script lang='ts'>
import {
ModalHeader,

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a modal for adjusting the grid settings.
-->
<script lang='ts'>
import { Column, Grid, Modal, NumberInput, Row, TextInput } from "carbon-components-svelte";

View File

@ -1,3 +1,8 @@
<!--
@component
This component is a modal that provides settings for changing the theme.
-->
<script lang='ts'>
import { Column, Dropdown, Grid, Modal, NumberInput, Row, TextInput } from "carbon-components-svelte";

View File

@ -1,3 +1,8 @@
<!--
@component
This component is a full 2D pixel editor.
-->
<script lang='ts'>
import { onMount } from 'svelte'

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides importing an indexed or RGBA PNG file with options for how to interpret columns and rows as groups and animations.
-->
<script lang='ts'>
import { GetFilePath, OpenFileBytes } from '../../wailsjs/go/main/App.js'
import { data } from '../../wailsjs/go/models.js'

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a modal for creating a new file.
-->
<script lang='ts'>
import { onMount } from 'svelte';
import { data } from '../../wailsjs/go/models.js'

View File

@ -1,3 +1,8 @@
<!--
@component
This component shows swatches of a given palette and provides controls for selecting, moving, and deleting swatches.
-->
<script lang='ts'>
import type { Color } from '../types/palette'
import { ReplaceSwatchUndoable, type LoadedFile, AddSwatchUndoable, MoveSwatchUndoable } from '../types/file'

View File

@ -1,3 +1,8 @@
<!--
@component
This component provides a sprite stack view of a file.
-->
<script lang='ts'>
import { Grid, Row, Column, Checkbox, Slider } from "carbon-components-svelte"
import type { LoadedFile } from "src/types/file"

View File

@ -1,5 +1,10 @@
import type { PixelPosition } from "./shapes"
/**
* @type {Canvas}
*
* Canvas provides a way to store and manipulate 2D pixel data.
*/
export class Canvas {
width: number
height: number
@ -36,21 +41,27 @@ export class Canvas {
return canvas
}
// clear sets all pixels to 0.
clear() {
for (let i = 0; i < this.pixels.length; i++) {
this.pixels[i] = 0
}
}
// refreshCanvas redraws the canvas with the current pixel data.
refreshCanvas() {
this.canvas.width = this.width
this.canvas.height = this.height
let ctx = this.canvas.getContext('2d')
ctx.putImageData(this.imageData, 0, 0)
}
// setPalette sets the palette to the provided value.
setPalette(palette: Uint32Array) {
this.palette = palette
}
// getPaletteAsRGBA returns the RGBA values of the palette color at the provided index.
getPaletteAsRGBA(index: number): { r: number, g: number, b: number, a: number } {
if (index < 0 || index >= this.palette.length) {
return { r: 0, g: 0, b: 0, a: 0 }
@ -63,12 +74,16 @@ export class Canvas {
a: (color >> 24) & 0xFF
}
}
// setPaletteFromUint8Array sets the palette to the provided value.
setPaletteFromUint8Array(palette: Uint8Array) {
this.palette = new Uint32Array(palette.length / 4)
for (let i = 0; i < palette.length; i += 4) {
this.palette[i / 4] = new Uint32Array(palette.buffer.slice(i, i + 4))[0]
}
}
// setPixelsFromUint8Array sets the pixel data to the provided value.
setPixelsFromUint8Array(pixels: Uint8Array) {
this.pixels = pixels
this.imageData = new ImageData(this.width, this.height)
@ -80,12 +95,16 @@ export class Canvas {
this.imageData.data[i * 4 + 3] = (color >> 24) & 0xFF
}
}
// getPixel gets the index at the provided pixel position.
getPixel(x: number, y: number): number {
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
return -1
}
return this.pixels[y * this.width + x]
}
// setPixel sets the index at the provided pixel position.
setPixel(x: number, y: number, index: number) {
this.pixels[y * this.width + x] = index
let color = this.palette[index]
@ -98,6 +117,8 @@ export class Canvas {
this.imageData.data[(y * this.width + x) * 4 + 2] = b
this.imageData.data[(y * this.width + x) * 4 + 3] = a
}
// setPixelRGBA sets the given pixel position to the provided RGBA values. If the RGBA values do not exist in the palette, they are automatically added.
setPixelRGBA(x: number, y: number, r: number, g: number, b: number, a: number) {
this.pixels[y * this.width + x] = this.addPaletteColor(r, g, b, a)
this.imageData.data[(y * this.width + x) * 4 + 0] = r
@ -105,6 +126,8 @@ export class Canvas {
this.imageData.data[(y * this.width + x) * 4 + 2] = b
this.imageData.data[(y * this.width + x) * 4 + 3] = a
}
// addPaletteColor adds the provided RGBA values to the palette if it does not already exist, and returns the index of the color in the palette.
addPaletteColor(r: number, g: number, b: number, a: number): number {
// Check if the color is already in the palette
for (let i = 0; i < this.palette.length; i++) {
@ -124,6 +147,8 @@ export class Canvas {
return index
}
// getClosestPaletteColor returns the RGBA and index values of the closest color in the palette to the provided RGBA values.
getClosestPaletteColor(r: number, g: number, b: number, a: number): {r: number, g: number, b: number, a: number, index: number} {
let similarityMap = this.palette.map((color) => {
let r2 = color & 0xFF
@ -148,9 +173,13 @@ export class Canvas {
index: closestIndex
}
}
// addNewPaletteColor adds the provided RGBA values to the palette.
addNewPaletteColor(r: number, g: number, b: number, a: number) {
this.palette = new Uint32Array([...this.palette, new Uint32Array([(a << 24) | (b << 16) | (g << 8) | r])[0]])
}
// insertPaletteColor inserts the provided RGBA values into the palette at the provided index.
insertPaletteColor(index: number, r: number, g: number, b: number, a: number) {
let newPalette = new Uint32Array(this.palette.length + 1)
for (let i = 0; i < index; i++) {
@ -162,6 +191,8 @@ export class Canvas {
}
this.palette = newPalette
}
// removePaletteIndex removes the palette entry at the provided index. This also updates the pixel data to reflect the change.
removePaletteIndex(index: number) {
if (index < 0) {
index = this.palette.length - index
@ -182,9 +213,13 @@ export class Canvas {
}
}
}
// replacePaletteColor replaces the palette entry at the provided index with the provided RGBA values.
replacePaletteColor(index: number, r: number, g: number, b: number, a: number) {
this.palette[index] = new Uint32Array([(a << 24) | (b << 16) | (g << 8) | r])[0]
}
// hasPaletteColor returns whether the palette contains the provided RGBA values.
hasPaletteColor(r: number, g: number, b: number, a: number): boolean {
for (let color of this.palette) {
if ((color & 0xFF) === r && ((color >> 8) & 0xFF) === g && ((color >> 16) & 0xFF) === b && ((color >> 24) & 0xFF) === a) {
@ -193,11 +228,15 @@ export class Canvas {
}
return false
}
// swapPaletteColors swaps the palette entries at the provided indices.
swapPaletteColors(index1: number, index2: number) {
let temp = this.palette[index1]
this.palette[index1] = this.palette[index2]
this.palette[index2] = temp
}
// movePaletteColor moves the palette entry at the provided index to the new index.
movePaletteColor(from: number, to: number) {
let temp = this.palette[from]
if (from < to) {
@ -211,9 +250,13 @@ export class Canvas {
}
this.palette[to] = temp
}
// setFakePalette updates the fake palette to the provided value.
setFakePalette(palette: Uint32Array | undefined) {
this.fakePalette = palette
}
// refreshImageData updates the ImageData with the current pixel data.
refreshImageData() {
let palette = this.palette
if (this.fakePalette) {
@ -231,7 +274,7 @@ export class Canvas {
}
}
// Returns the an ImageData containing the canvas contents clipped to the provided pixel mask.
// getImageDataFromMask the ImageData containing the canvas contents clipped to the provided pixel mask.
getImageDataFromMask(mask: PixelPosition[]): {imageData: ImageData, x: number, y: number, w: number, h: number} {
// Get minimum x position from mask.
let minX = 9999999

View File

@ -1,3 +1,4 @@
// floatsToBytes converts 0-1 to a 0-255 range.
function floatsToBytes(colors: number[]): number[] {
if (colors.length <= 0) return colors
if (colors[0] <= 1.0) {
@ -8,6 +9,7 @@ function floatsToBytes(colors: number[]): number[] {
return colors
}
// bytesToFloats converts 0-255 to a 0-1 range.
function bytesToFloats(colors: number[]): number[] {
if (colors.length <= 0) return colors
if (colors[0] > 1.0) {
@ -18,6 +20,7 @@ function bytesToFloats(colors: number[]): number[] {
return colors
}
// HSV2RGB converts HSV to RGB.
export function HSV2RGB(hsv: number[]): number[] {
let hh: number, p: number, q: number, t: number, ff: number, i: number
let rgb: number[] = []
@ -75,6 +78,7 @@ export function HSV2RGB(hsv: number[]): number[] {
return floatsToBytes(rgb)
}
// RGB2HSV converts RGB to HSV.
export function RGB2HSV(rgb: number[]): number[] {
rgb = bytesToFloats(rgb)

View File

@ -1,5 +1,6 @@
import type { PixelPosition } from "./shapes"
// SelectionArea is basically a canvas and pixel mask that is used to represent a selection area. It provides marching ants to visualize the selection.
export class SelectionArea {
public marchingCanvas: HTMLCanvasElement
private marchStep: number = 0

View File

@ -1,9 +1,11 @@
// PixelPosition represents a coordinate and index.
export interface PixelPosition {
x: number
y: number
index: number
}
// FilledCircle returns an array of PixelPositions that correspond to a filled circle.
export function FilledCircle(x: number, y: number, radius: number, index: number): PixelPosition[] {
let pixels: PixelPosition[] = []
@ -18,6 +20,7 @@ export function FilledCircle(x: number, y: number, radius: number, index: number
return pixels
}
// FilledSquare returns an array of PixelPositions that correspond to a filled square.
export function FilledSquare(x: number, y: number, size: number, index: number): PixelPosition[] {
let pixels: PixelPosition[] = []
@ -32,6 +35,7 @@ export function FilledSquare(x: number, y: number, size: number, index: number):
return pixels
}
// RandomSpray returns an array of PixelPositions that correspond to a random spray of pixels.
export function RandomSpray(x: number, y: number, radius: number, density: number, index: number): PixelPosition[] {
let pixels: PixelPosition[] = []

View File

@ -16,6 +16,7 @@ interface Pointer {
export type BrushType = "circle" | "square"
// Tool is an interface that receives pointer events and can act upon a ToolContext (which contains data such as the current file).
export interface Tool {
isActive(): boolean
pointerDown(ctx: ToolContext, ptr: Pointer): void
@ -23,34 +24,41 @@ export interface Tool {
pointerUp(ctx: ToolContext, ptr: Pointer): void
}
// BrushToolContext provides context specific to the brush tool.
export interface BrushToolContext {
brushSize: number
brushType: BrushType
colorIndex: number
}
// SprayToolContext provides context specific to the spray tool.
export interface SprayToolContext {
radius: number
density: number
colorIndex: number
}
// EraserToolContext provides context specific to the eraser tool.
export interface EraserToolContext {
brushSize: number
brushType: BrushType
}
// FloodToolContext provides context specific to the flood tool.
export interface FloodToolContext {
colorIndex: number
}
// SelectionToolContext provides context specific to the selection tool.
export interface SelectionToolContext {
}
// PickerToolContext provides context specific to the picker tool.
export interface PickerToolContext {
setColorIndex(index: number): void
}
// BrushTool is a tool that allows the user to draw with a brush.
export class BrushTool implements Tool {
private lastX: number
private lastY: number
@ -139,6 +147,7 @@ export class BrushTool implements Tool {
}
}
// EraserTool is basically the BrushTool, but with the color index set to 0 (meaning a transparent pixel).
export class EraserTool extends BrushTool {
pointerDown(ctx: ToolContext & EraserToolContext, ptr: Pointer) {
super.pointerDown({...ctx, colorIndex: 0}, ptr)
@ -148,6 +157,7 @@ export class EraserTool extends BrushTool {
}
}
// SprayTool implements a spray can tool.
export class SprayTool implements Tool {
private active: boolean
private lastX: number
@ -192,6 +202,7 @@ export class SprayTool implements Tool {
}
}
// FillTool implements a flood fill tool.
export class FillTool implements Tool {
private active: boolean
isActive(): boolean {
@ -239,6 +250,7 @@ export class FillTool implements Tool {
}
}
// PickerTool allows picking the color index from the canvas.
export class PickerTool implements Tool {
private active: boolean
isActive(): boolean {
@ -263,6 +275,7 @@ export class PickerTool implements Tool {
}
}
// SelectionTool allows selecting an area of the canvas.
export class SelectionTool implements Tool {
private active: boolean
private startX: number
@ -332,6 +345,7 @@ export class SelectionTool implements Tool {
}
}
// MagicWandTool implements a magic wand tool.
export class MagicWandTool implements Tool {
private active: boolean
@ -385,6 +399,7 @@ export class MagicWandTool implements Tool {
}
}
// MoveTool implements a tool to move pixels within a selection.
export class MoveTool implements Tool {
private active: boolean
private startX: number

View File

@ -1,3 +1,4 @@
// UndoableStack provides an undo/redo system.
export class UndoableStack<T> {
private target: T;
private stack: Undoable<T>[] = [];
@ -97,11 +98,13 @@ export class UndoableStack<T> {
}
}
// Undoable is an interface for undoable actions stored in the stack.
export interface Undoable<T> {
apply(t: T): void;
unapply(t: T): void;
}
// UndoableGroup is a group of undoable actions. This is used to group multiple actions, such as drawing with a pen, into one undoable action.
export class UndoableGroup<T> {
private items: Undoable<T>[];