Last Updated: March 1, 2026
Version: 1.1.0
Status: Production Ready ✨
Web App: gumdropstudio.app
Docs: docs.gumdropstudio.app
npm: @pinkpixel/gumdrop-studio
Releases: GitHub Releases
Deployment: Cloudflare Pages (browser) + Tauri 2 (desktop)
Gumdrop Studio is a modern, browser-based pixel art creation app built with React 19. It features a unique dual-layer canvas system that combines blocky pixel art with smooth vector overlay accents—perfect for creating cute pixelated characters, pets, icons, and more!
The app offers a complete set of drawing tools, flexible export options, and project management capabilities, all wrapped in a delightful Pink Pixel-branded UI with glassmorphism aesthetics and emoji-driven UX.
Create adorable pixel art with ease! Gumdrop Studio is designed for:
- Digital artists who want a quick, browser-based pixel art tool
- Game developers needing sprite creation and editing
- Hobbyists looking to make cute pixel pets, icons, or avatars
- Educators teaching pixel art fundamentals
The dual-layer system is the killer feature: draw blocky pixel art on one layer, then add fine details like whiskers, smiles, or highlights on a smooth vector overlay layer.
| Technology | Version | Purpose |
|---|---|---|
| React | 19.2.4 | UI framework |
| Vite | 7.3.1 | Build tool & dev server |
| Tailwind CSS | 4.1.18 | Styling framework |
| @tailwindcss/postcss | 4.1.18 | PostCSS plugin |
| Tauri | 2.x | Native desktop app framework |
| Rust | stable | Tauri backend language |
| Autoprefixer | 10.4.24 | CSS vendor prefixing |
All dependencies are latest stable versions as of March 2026.
gumdrop-studio/
├── public/
│ ├── favicon.png # 🧁 Favicon
│ ├── logo.png # Brand logo
│ ├── _redirects # Cloudflare Pages SPA routing
│ └── _routes.json # Cloudflare routing config
├── src/
│ ├── components/
│ │ ├── Canvas.jsx # Dual-canvas rendering + pointer events
│ │ ├── ColorWheel.jsx # HSV color picker
│ │ ├── Footer.jsx # Footer component
│ │ ├── Header.jsx # Header with theme toggle
│ │ ├── ProjectPanel.jsx # Save/load/export UI
│ │ └── ToolPanel.jsx # Tool selection + color controls
│ ├── utils/
│ │ ├── canvas.js # Canvas rendering helpers
│ │ ├── colors.js # Color space conversions
│ │ ├── desktop.js # Tauri menu event listeners (noop in browser)
│ │ ├── helpers.js # Generic utilities
│ │ ├── rasterizers.js # Shape-to-pixel algorithms
│ │ └── storage.js # Project persistence (localStorage / Tauri store)
│ ├── App.jsx # Main app state + logic
│ ├── index.css # Global styles
│ └── main.jsx # React entry point
├── src-tauri/
│ ├── src/main.rs # Tauri entry point + native menu
│ ├── Cargo.toml # Rust dependencies
│ ├── tauri.conf.json # Window size, bundle, permissions
│ └── icons/ # App icons (PNG, ICO, ICNS)
├── index.html # Vite entry HTML
├── package.json # Dependencies + scripts
├── vite.config.js # Vite config (port 1234)
├── tailwind.config.js # Tailwind config
├── postcss.config.js # PostCSS config
├── wrangler.toml # Cloudflare Pages config
└── README.md
| Tool | Description | Implementation |
|---|---|---|
| Pixel Stamp ⬛ | Precise single-pixel placement (default tool) | Uses shape preview flow for single cell commits |
| Pencil 🖊️ | Freehand pixel drawing | Direct pixel modification |
| Eraser 🧽 | Remove pixels | Sets pixels to null |
| Line 📏 | Straight lines | Bresenham's algorithm |
| Rectangle ▭ | Outline or filled boxes | Rasterization with fill toggle |
| Circle ◯ | Perfect circles | Midpoint circle algorithm |
| Curve ☾ | Smooth Bezier curves | 3-click quadratic Bezier (200 steps) |
| Accent Pen ✨ | Thin vector lines | Overlay layer with anti-aliasing |
- HSV Color Wheel: Circular picker for hue + saturation
- Alpha Channel: Full transparency support (0.0–1.0)
- Hex Input: Direct color code entry
- RGBA Export: All export formats support transparency
- Adjustable Zoom: 4x to 40x scale (default 40x, auto-fits to viewport)
- Grid Overlay: Toggle-able pixel grid
- Theme-Aware Grid: Grid stroke color adapts to light/dark palettes for consistent visibility
- Softer Light Theme: Warm cream gradient tones reduce glare and keep panels legible
- Dark/Light Mode Toggle: User-selectable theme in header
- Dual Layers:
- Pixel Layer: 2D array of RGBA values (blocky, crisp edges)
- Overlay Layer: Vector paths for smooth thin lines (1-6px width)
- Canvas Sizes: 4×4 to 256×256 pixels (default 40×40)
- Save/Load: Projects stored in browser localStorage
- Auto-Save: Updates to
gumdrop:projectsnamespace - Project List: Browse and manage saved projects
- New Project: Quick canvas reset
| Format | Description | Use Case |
|---|---|---|
| PNG | Transparent or opaque background | Web graphics, sprites |
| JPG | Opaque background, smaller file size | Photos, backgrounds |
| SVG | Vector format (pixels as <rect>, paths as <polyline>) |
Scalable graphics |
| JSON | Complete state data | Backup, sharing, editing |
| HTML Snippet | Self-contained canvas renderer | Embeddable demos |
- Undo/Redo: Full state snapshots (pixels + overlay)
- Deep Copies: Immutable state updates prevent bugs
- Unlimited History: Limited only by browser memory
All state lives in App.jsx with immutable updates:
// State structure
gridW: 40 // Canvas width (default)
gridH: 40 // Canvas height (default)
scale: 40 // User's preferred zoom (default)
renderScale: number // Auto-fitted scale for display
pixels: Array[][] // pixels[y][x] = { r, g, b, a } | null
overlayPaths: Array[] // [{ id, points: [{x,y}], color, width }]
history: Array[] // [{ pixels, overlay }]
future: Array[] // Redo stack [{ pixels, overlay }]
tool: string // Current tool ID (default: 'stamp')
color: string // Hex color (default: '#ff66cc')
alpha: number // Transparency 0-1 (default: 1)
accentWidth: number // Accent pen width (default: 1)
showGrid: boolean // Grid visibility (default: true)
fillShape: boolean // Rectangle fill toggle (default: false)
darkMode: boolean // Theme preference (default: true)
tempPreview: Array[] // Shape preview coordinates
curveTemps: Array[] // Curve tool state (3-click)Deep Copying Pattern:
setPixels((prev) => {
const copy = deepCopyPixels(prev);
copy[y][x] = newValue;
return copy;
});Pointer tracking for tools such as Pixel Stamp and Pencil relies on useRef (dragStartRef) so cursor updates don't wait for React renders, eliminating streak artifacts when switching cells quickly.
Dual-Canvas Architecture:
-
Pixel Canvas:
- Renders pixel array to
ImageData imageSmoothingEnabled = falsefor crisp edges- Scales up using CSS transform (no blur)
- Renders pixel array to
-
Overlay Canvas:
- Renders vector paths with
Canvas2DAPI imageSmoothingEnabled = truefor anti-aliasing- Positioned absolutely on top of pixel canvas
- Renders vector paths with
Drawing Flow:
PointerDown → Push history → Begin drawing
PointerMove → Update tempPreview (shapes) or apply pixels (freehand)
PointerUp → Commit final shape → Clear preview
All shapes convert to [x, y] coordinate arrays before applying:
- Line: Bresenham's line algorithm (
rasterLine) - Circle: Midpoint circle algorithm (
rasterCircle) - Rectangle: Outline via
rasterRector filled viafillRect - Curve: 200-step parametric Bezier (
rasterQuad)
Example from rasterizers.js:
export function rasterLine(x0, y0, x1, y1) {
const coords = [];
// Bresenham's algorithm implementation
// Returns array of [x, y] pairs
return coords;
}- Grid Coordinates: Integer positions
(x, y)from(0, 0)to(gridW-1, gridH-1) - Screen Coordinates: Converted via
getGridPos(e)usinggetBoundingClientRect() - Display Scale: Grid units ×
renderScalefactor (auto-fits to viewport) - User Scale: Separate
scalestate (4x-40x, default 40x) controlled by zoom slider - Auto-fit:
renderScalecomputed to fit canvas within viewport while respecting user'sscaleas maximum - Example: 40×40 grid at 40x scale = 1600×1600px display (auto-scales down if viewport smaller)
Utilities in colors.js:
hexToRGBA(hex, alpha)→{ r, g, b, a }rgbaToHex({ r, g, b })→"#rrggbb"hsvToRgb(h, s, v)→{ r, g, b }drawColorWheel(canvas)→ Renders HSV picker
Color Picker Algorithm:
// HSV picker: hue = angle, saturation = distance from center
const hue = (Math.atan2(dy, dx) / (2 * Math.PI) + 1) % 1;
const sat = distance / radius;LocalStorage Schema:
{
"id": "abc123",
"name": "My Pixel Pet",
"updated": 1729800000000,
"gridW": 32,
"gridH": 32,
"pixels": [[...]],
"overlayPaths": [...]
}Namespace: gumdrop:projects
Operations: listProjects(), saveProject(), loadProject(), deleteProject()
- Color Palette: Pink/purple/indigo gradients
- Style: Glassmorphism with
backdrop-blur - Borders: White with low opacity (
ring-white/10) - Corners: Rounded (
rounded-xl,rounded-2xl) - Typography: System UI sans-serif
- Icons: Emoji-based labels (🖊️, 🧽, 📏, etc.)
3-Column Responsive Grid:
[ToolPanel] [Canvas] [ProjectPanel]
300px 1fr 280px
On mobile/tablet: Stacks vertically
- Signature: "Made with 💗 by Pink Pixel"
- Tagline: "Go draw some pixelated kittens!"
- Color Theme: Pink (
#ff66cc) + Purple + Indigo
npm installnpm run devOpens at http://localhost:1234 (auto-opens browser)
npm run tauri:devLaunches the Tauri desktop window with hot reload.
npm run buildOutput: dist/ directory
npm run tauri:buildOutput: src-tauri/target/release/bundle/
nsis/— Windows NSIS installer (.exe)appimage/— portable AppImagedeb/— Debian/Ubuntu package
Windows: Built automatically via GitHub Actions (
.github/workflows/release.yml) on a Windows runner. Download from GitHub Releases or docs.gumdropstudio.app.
npm run previewnpm run deploy # Deploy to preview
npm run deploy:production # Deploy to productionOr deploy manually:
wrangler pages deploy distexport default defineConfig({
plugins: [react()],
server: {
port: 1234,
open: true,
},
});Uses default theme, scans:
./index.html./src/**/*.{js,ts,jsx,tsx}
{
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "^2.4.2",
"@tailwindcss/postcss": "^4.1.18",
"react": "^19.2.4",
"react-dom": "^19.2.4"
}{
"@tauri-apps/cli": "^2.10.0",
"@vitejs/plugin-react": "^5.1.4",
"autoprefixer": "^10.4.24",
"tailwindcss": "^4.1.18",
"vite": "^7.3.1",
"wrangler": "^4.65.0"
}All dependencies are latest stable versions ✅
-
Canvas Size Limits:
- Practical limit: ~256×256 pixels
- Deep copying pixel array is O(n) where n = gridW × gridH
- Acceptable for small canvases (≤128×128)
-
Rendering Optimizations:
- OffscreenCanvas used when available
- Clear before redraw (
clearRect) - Image smoothing disabled for pixel layer
-
Memory:
- History limited only by browser memory
- Each undo state stores complete pixel + overlay copy
- Consider implementing dirty rectangle tracking for large canvases
- Requires: ES2015+ features (arrow functions, destructuring)
- LocalStorage: Required for project persistence
- Canvas API: Required for rendering
- OffscreenCanvas: Optional (graceful fallback)
Tested on: Chrome, Firefox, Safari (modern versions)
- Functional components with hooks throughout
- Props passed explicitly (no context providers)
- Canvas refs forwarded via
React.forwardRef
Always immutable:
// ✅ Correct
setPixels((prev) => {
const copy = deepCopyPixels(prev);
copy[y][x] = newValue;
return copy;
});
// ❌ Wrong - mutates state
pixels[y][x] = newValue;
setPixels(pixels);- Pure functions with defensive checks
- Validate inputs with
toInt(),validXY() - Return empty arrays on invalid input (never throw)
- Null-safe operators (
?.,??)
- More Tools: Flood fill, spray paint, selection tools
- Layers: Multiple pixel layers with blend modes
- Animation: Frame-by-frame pixel animation
- Palettes: Save/load color palettes
- Filters: Blur, sharpen, color adjustments
- Collaboration: Real-time multi-user editing
- Cloud Save: Optional account + cloud storage
Apache 2.0 (see LICENSE file)
- Web App: gumdropstudio.app
- Docs: docs.gumdropstudio.app
- Releases: github.com/pinkpixel-dev/gumdrop-studio/releases
- npm: npmjs.com/package/@pinkpixel/gumdrop-studio- Author: Pink Pixel
- Website: pinkpixel.dev
- GitHub: github.com/pinkpixel-dev
- Contact: admin@pinkpixel.dev
Made with 💗 by Pink Pixel — Dream it, Pixel it ✨