---
id: gh-msw-painter
name: "msw-painter"
url: https://skills.yangsir.net/skill/gh-msw-painter
author: msw-git
domain: game-dev
tags: ["pixel-art", "sprite-creation", "game-assets", "image-generation"]
install_count: 2600
rating: 4.40 (120 reviews)
github: https://github.com/msw-git/msw-ai-coding-plugins-official/tree/main/plugins/msw-maker-base-skill/skills/msw-painter
---

# msw-painter

> 此技能允许用户通过 SVG/Canvas/HTML 代码直接绘制像素艺术精灵图，并将其渲染为 PNG 格式，上传以获取资源 RUID。它支持复古像素和卡通两种风格，为游戏开发或自定义图形提供灵活的精灵创作能力。

**Stats**: 2,600 installs · 4.4/5 (120 reviews)

## Before / After 对比

### 自定义精灵图创建效率

**Before**:

用户/开发者需要花费大量时间手动绘制像素艺术，或在现有资源库中反复搜索，但往往难以找到完全符合项目风格和需求的精灵图，导致开发进度受阻或视觉效果不统一。

**After**:

此技能让用户能够通过代码快速生成符合特定风格（如复古或卡通）的像素艺术精灵图，并立即获得可用的资源 RUID，大大缩短了自定义图形的创作周期，确保了视觉风格的一致性。

| Metric | Before | After | Change |
|---|---|---|---|
| 自定义精灵图创建时间 | 120分钟 | 15分钟 | -87.5% |

## Readme

# MSW Painter

A workflow for registering a hand-drawn pixel art sprite as a sprite resource. **Call `msw-search` first, and only invoke this skill when no suitable RUID is found.**

This skill is dedicated to the sprite category. It does not handle animation / audio / avatar / atlas.

The painter supports two pixel art **styles**: **chunky pixel** (retro, icon/tile feel) and **maple cartoon** (MapleStory-inspired, character/NPC feel). Pick one before writing code — see step 2 below.

---

## When to invoke

| Situation | Action |
|-----------|--------|
| User wants a specific sprite | First use `msw-search` (Resource search section, sprite category) |
| `msw-search` returns an RUID that matches the intent | Use that RUID directly. **Do not invoke painter.** |
| No search results, or all results are unsuitable | Invoke painter → create directly |
| User explicitly says "I need a hand-drawn looking character/icon" | Invoke painter directly |

---

## Workflow

1. **Choose the medium** — One of SVG / Canvas / HTML. See "Choosing the medium" below.
2. **Choose the style** — `chunky` or `maple`. See "Choosing the style" below.
3. **Decide the size** — See [references/size-guide.md](references/size-guide.md). Default is 128×128.
4. **Write the code** — Follow the rules for the chosen style:
   - `chunky` → [references/style-chunky-pixel.md](references/style-chunky-pixel.md)
   - `maple` → [references/style-maple-cartoon.md](references/style-maple-cartoon.md)
5. **Render to PNG** — Run `scripts/render.cjs`.
6. **Upload the resource** — `mcp__msw-mcp__asset_create_resource_storage_item` two-step pattern.
7. **Report the result** — RUID + a 1–2 sentence description (include which style was used). Entity placement / script application is outside the painter's scope.

---

## 1. Choosing the medium

| Medium | Recommended use | Strengths |
|--------|-----------------|-----------|
| **SVG** | Icons, logos, simple characters, shape-based pixel art | Intuitive code, easy to drop 1px `<rect>` dots |
| **Canvas** | Procedural patterns, iterative logic (loop-drawn textures / noise) | Generate complex patterns via JS programming logic |
| **HTML** | Composite layouts that can be styled quickly with CSS | Rarely used — SVG/Canvas is usually a better fit for pixel art |

### Minimal SVG template

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
     width="100%" height="100%" preserveAspectRatio="xMidYMid meet"
     style="image-rendering: pixelated;">
  <rect x="6" y="2" width="1" height="1" fill="#4A90D9"/>
  <!-- Place dots one by one with 1px rects -->
</svg>
```

> ⚠️ Use `width="100%" height="100%"` (NOT a fixed pixel count). The SVG element draws at its **own** declared size inside the render.cjs viewport — if you hard-code 128 but render at `--width 1024`, the SVG fills only the top-left 128px and the rest of the PNG is transparent. `100%` makes the SVG fill whatever canvas `--width`/`--height` specifies.

### Minimal Canvas template

```javascript
// `c` (canvas element) and `ctx` (2D context) are auto-exposed by render.cjs.
// ctx.imageSmoothingEnabled = false is applied automatically as well.
// IMPORTANT: derive scale from c.width, not a hard-coded constant — otherwise
// a different --width leaves the bottom-right of the canvas blank.
const GRID = 16;
const scale = c.width / GRID;  // 16×16 logical grid → canvas-sized output
ctx.fillStyle = '#4A90D9';
ctx.fillRect(6 * scale, 2 * scale, scale, scale);
```

### Minimal HTML template

```html
<!doctype html>
<html><body style="margin:0; image-rendering: pixelated;">
  <!-- Anything you like -->
</body></html>
```

---

## 2. Choosing the style

| Style | Recommended use | Look & feel | Logical grid | Outline | Shading |
|-------|-----------------|-------------|--------------|---------|---------|
| **`chunky`** | Icons, buttons, tiles, blocks, simple props | Retro / 8-bit / NES-SNES | Small (16×16, 32×32) | Black or white, 1px | 2–4 stepped levels, NO AA |
| **`maple`** | Characters, NPCs, monsters, cute mascots | MapleStory / storybook / cartoon | Larger (32×32 ~ 128×128) | **Selout** (darker version of fill color) | 4–6 stepped levels + **selective AA** on silhouette + optional 2×2 dithering |

### Defaults when in doubt

- Icon / button / tile / block → **`chunky`**
- Character / NPC / monster / mascot / "cute" requests / "draw a slime" → **`maple`**
- User says "retro" / "8-bit" / "NES" / "minimal" → **`chunky`**
- User says "MapleStory" / "cute" / "cartoon" / "chibi" / "illustrated" → **`maple`**

Full per-style rules:
- [references/style-chunky-pixel.md](references/style-chunky-pixel.md)
- [references/style-maple-cartoon.md](references/style-maple-cartoon.md)

Both styles share the same forbidden APIs (no curve APIs, no gradient APIs, no fractional coordinates, no `filter: blur`/`drop-shadow`). They differ in palette richness, outline color, AA, and working grid.

---

## 3. Size guide (summary)

| Use | Recommended size |
|-----|------------------|
| Icon / button | 48×48 ~ 64×64 |
| Character / item / NPC / monster | 96×96 ~ 128×128 |
| Tile / floor / block | 64×64 ~ 128×128 |
| Background / large object | 256×256 or larger (only on explicit request) |

The default is **128×128**. For style-specific working-grid tables (chunky uses a small logical grid like 16×16; maple uses a larger one like 64×64) and SD character proportions, see [references/size-guide.md](references/size-guide.md).

> If the requested output is **below 64×64**, the `maple` style does not have enough pixels for selout + AA + facial features — either bump the output size to 64+ or fall back to `chunky`.

---

## 4. PNG render — `render.cjs`

### One-time dependency install

```bash
cd scripts && npm ci
```

This installs `puppeteer` (~200MB including headless Chromium) from the committed `package-lock.json`. It is separate from other base skill dependencies, so run this only the first time you use painter.

> 🔒 Use `npm ci`, **not** `npm install`. `npm ci` installs exactly the versions pinned in `package-lock.json` and fails if the lockfile and `package.json` disagree — this is the supply-chain integrity guarantee for W012. Never edit `package-lock.json` by hand; if you need to bump puppeteer, run `npm install puppeteer@<version>` locally and commit the regenerated lockfile.

### Sandboxing & network isolation

`render.cjs` runs the headless Chromium with the OS sandbox **enabled** by default and blocks **all** network requests from the rendered page. The page is also served via a `data:` URL with a strict `Content-Security-Policy` (`default-src 'none'`), and the SVG / HTML input is sanitized to strip `<script>`, `<foreignObject>`, inline `on*` handlers, and non-`data:` URLs. You do not need to do anything to opt in — these protections are always on.

If you are in a constrained environment where Chromium cannot start its sandbox (some CI containers, certain WSL setups), set `PAINTER_DISABLE_SANDBOX=1` before invoking `render.cjs`. **Do not set this on a developer workstation.**

### Invocation

```bash
node scripts/render.cjs --type <svg|canvas|html> --in <code-file> --out <out.png> --width <W> --height <H>
```

Or pass the code via stdin:

```bash
echo "<svg ...>" | node scripts/render.cjs --type svg --out out.png --width 128 --height 128
```

Options:
- `--type`: One of `svg` / `canvas` / `html`. **Required**.
- `--in`: Path to the code file. Omit or use `-` for stdin.
- `--out`: Output PNG path. **Required**.
- `--width` / `--height`: Output pixel size. Default 128.

On success, the absolute path of the output PNG is printed to stdout on a single line and exit code is 0. On failure, the error is printed to stderr and exit code is 1.

The PNG defaults to a transparent background. If you need a background color, draw it explicitly inside the SVG/Canvas/HTML.

---

## 5. Resource upload — two-step pattern

`mcp__msw-mcp__asset_create_resource_storage_item` is called twice.

> 🔒 **Security — handling the presigned URL (W007).** The `presignedUrl` returned in step 1 is a short-lived signed credential (anyone holding it can PUT to that storage slot until it expires). Treat it as a secret:
>
> - **Never** echo, quote, paraphrase, or include the URL or any of its query parameters (`X-Amz-Signature`, `X-Amz-Credential`, etc.) in the assistant's user-facing response, in commit messages, in logs, or in any subsequent prompt — including when reporting "what you did".
> - When invoking the shell, pass the URL via the `PAINTER_PRESIGNED_URL` environment variable as shown below, **not** as a command-line argument. Command-line arguments are visible to other processes via `/proc/*/cmdline` (Linux/macOS) and `Get-Process` (Windows), and they are recorded in shell history.
> - When invoking step 3, pass the URL directly as the `fileUrl` tool argument — do **not** copy it into a code block or markdown for the user to see first.
> - If the PUT step fails (typically `401`/`403` → URL expired), discard the URL and restart from step 1. Do not reuse it elsewhere.

### Step 1 — request a presigned URL

```
mcp__msw-mcp__asset_create_resource_storage_item({
  category: "sprite",
  subcategory: "<appropriate subcategory>",   // e.g. "monster", "npc", "object", "icon"
  name: "<resource name>",
  description: "<1–2 sentence description>",
  makerOwnerType: 0,                          // 0 = Account
  makerOwnerId: "<account id>",               // look up in advance with mcp__msw-mcp__account_get_my_user_id
  // omit fileUrl in this step
})
```

The response contains a `presignedUrl`. Keep it inside the agent's reasoning context only — do **not** surface it in chat output.

### Step 2 — PUT the PNG binary (URL passed via env var)

PowerShell:
```powershell
$env:PAINTER_PRESIGNED_URL = "<presignedUrl from step 1>"
try {
  Invoke-WebRequest -Method PUT -InFile out.png -Uri $env:PAINTER_PRESIGNED_URL -ContentType "image/png"
} finally {
  Remove-Item Env:\PAINTER_PRESIGNED_URL -ErrorAction SilentlyContinue
}
```

bash (Git for Windows / WSL):
```bash
PAINTER_PRESIGNED_URL="<presignedUrl from step 1>" \
  curl -X PUT -T out.png "$PAINTER_PRESIGNED_URL" && \
  unset PAINTER_PRESIGNED_URL
```

The PUT itself is a plain binary upload — no auth headers are needed (the signature is embedded in the presigned URL). The env-var pattern keeps the URL out of `Get-Process` / `ps`-visible argument lists and out of shell history.

### Step 3 — report upload completion

```
mcp__msw-mcp__asset_create_resource_storage_item({
  ...same arguments,
  fileUrl: "<presignedUrl from step 1>"   // pass directly as tool arg, do not echo
})
```

The response contains the sprite **RUID**. That is the final deliverable. After this call returns, treat the URL as fully consumed — do not retain it.

### Choosing a subcategory

First inspect the subcategory distribution of existing sprites with `asset_search_resources` or `asset_list_account_resources` and match it. When in doubt, fall back to a generic value such as `object` / `etc`.

---

## 6. Report format

When the painter task is done, hand the user only this:

```
RUID: <received RUID>
Style: <chunky | maple>
<1–2 sentence description: what you drew, at what size, and what sprite it was registered as>
```

Entity creation/movement/spawn, script authoring, and UI editing are outside the painter's scope. Handle those in another skill or a follow-up step.

---

## Common pitfalls

- **Not running `npm ci` before `render.cjs`** → `Cannot find module 'puppeteer'`. Only needed the first time. Use `npm ci` (not `npm install`) so the lockfile-pinned puppeteer version is installed.
- **Omitting `--width` / `--height`** → It falls back to 128×128, and if the user wanted a different size you have to redraw. Always specify it.
- **SVG/Canvas content drawn only in the top-left corner of the PNG** → The drawing code declared its own dimensions (e.g. SVG `width="128" height="128"` or Canvas `scale = 8`) but render.cjs was invoked with a larger `--width`/`--height`. The content fills only its declared size and the rest of the PNG stays transparent. Fix: SVG uses `width="100%" height="100%"`; Canvas derives scale from `c.width`. The Minimal templates above already follow this.
- **Always Read the output PNG before uploading** → A misconfigured SVG/Canvas can silently produce a blank or off-canvas PNG. One `Read` on the output catches the size-mismatch and blank-canvas bugs in seconds; uploading first means re-doing the 2-step upload.
- **Background comes out black** → You drew a background inside the SVG/Canvas/HTML. To keep it transparent, remove the background shape itself.
- **Curves look smooth** → If using `chunky`, this is a rule violation; remove `arc()`/`bezierCurveTo()`/gradients and redraw with dots. If using `maple`, smoothness should come from **selective AA pixels at the silhouette**, NOT from gradient/curve APIs — the API ban still applies.
- **Maple sprite looks like chunky with extra colors** → You probably forgot the **selout** (1-pixel darker-color outline around each surface) and/or the selective AA at silhouette edges. Re-check `style-maple-cartoon.md` Selout and Selective AA sections.
- **Chunky sprite looks mushy / blurry** → You added intermediate-color pixels on edges. Chunky forbids ALL anti-aliasing — remove transition pixels and keep edges sharp. If a softer look is desired, switch to `maple` instead.
- **Maple sprite at small size (32×32 output) looks bad** → Maple style needs ≥ 64×64 output to fit selout + AA + features. Either increase size or switch to `chunky`.
- **PUT step fails with 401/403** → The presigned URL expired or is wrong. Restart from step 1.
- **Changing other metadata in the step-2 completion call** → Pass the exact same `category`/`subcategory`/`name`/`description`/`makerOwnerType`/`makerOwnerId` as in step 1. Only add `fileUrl`.


---
*Source: https://skills.yangsir.net/skill/gh-msw-painter*
*Markdown mirror: https://skills.yangsir.net/api/skill/gh-msw-painter/markdown*