# SpriteDX - Designing Template Picker

## Struggle

This week felt rough. I wanted to be the guy charging ahead at light speed, dropping a sharp demo every week. But with jet lag, losing Monday to travel, and master’s deadlines piling up, I just couldn’t get there.

The goal was clear: take the hardcoded pipeline, convert it to JSON, and use that JSON to dynamically render the Parameter Board. I got halfway, but not all the way. It felt like stopping mid-shit, forced to get up and deal with daily chores instead of finishing properly.

Part of the frustration came from a wavering heart about the Pipeline Editor. I believe it’s the right path forward, but in the short term, hardcoded pipelines are enough to deliver an initial demo. That editor doesn’t actually create much “wow factor“ up front.

To me, the Pipeline Editor is the main course—the bread and butter of SpriteDX. But when people judge a product, the appetizer sets the mode.The meal still has to deliver, but the first taste often decides everything.

And the Pipeline Editor is really a pro tool. Like Roblox, where the majority play games but only a small percentage build them. In the same way, the time I put into the editor will matter most to people who are already invested.

Anyway, just ranting. I know I’m on the right track by focusing on the editor now—it’s foundational—but it still feels like grunt work that drifts into obscurity.

---

## Image Picker

The first piece of puzzle is the image picker. Originally, we have named this a Template Picker since we are using it primarily to load the template images **and configure additional parameters**. In the example below, when the template is selected, it provides following information to the pipeline:

1. Template Image
    
2. Mask (via alpha channel)
    
3. Num Rows
    
4. Num Columns
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1757284628969/588d6f63-08a3-4d56-aa37-228cf8bebad3.png align="center")

We need all that information, only then can we use the template. So, if we were to use generic “image picker“ to double as a template picker, we will need the ability to pull in some additional information.

Alternatively, we can always have multiple inputs separately. That would actually be most straightforward. Here is how it would look if we were to do that.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1757285798694/37d7f65a-e0c5-40c0-8103-c60855277e0e.png align="center")

The issue is that when user selects an image, we also need to auto set the Rows and Columns. This is tricky because the Rows and Columns are not associated with the image.

That brings us to the idea of separate “template picker“ component which will not only load a template image but a hash of properties of the template. In a way building a template picker makes total sense in that when we think about a template, it can be any key value pairs. In this particular pipeline, we need to get those four information—image, mask, numRows, numCols, but in other situations we many want a template picker that picks set of properties like string and booleans.

---

## Designing Generic Template Picker

Let’s pivot and design a generic Template Picker which is can handle setting of key-value pairs. Let’s start with current definition:

```javascript
"inputs": {
  "templateImage": {
    "type": "image",
    "label": null,
    "options": [{ "label": "Template 1", "value": "template1.png" }]
  },
  "numRows": {
    "type": "number",
    "label": "Rows"
  },
  "numColumns": {
    "type": "number",
    "label": "Columns"
  }
}
```

Basically, I need to define select box which will set `templateImage`, `numRows`, and `numColumns`, and we want to make it so that the schema is flexible enough to handle different situations.

The schema for template picker would go something like this:

```json
"inputs": {
  "template": {
    "type": { 
      "type": "object",
      "fields": {
        "templateImage": "image",
        "row": "number",
        "col": "number"
      }
    },
    "options": [{ 
      "label": "Template 1", 
      "value": { 
        "templateImage": "template1.png",
        "numRows": 2,
        "numColumns": 4
      }
    }]
  }
}
```

In this example, there isn’t any “template“ specific code but it is a generic “object” bag picker where it vends multiple key value pairs.

One limitation to this design is that these object definitions are not quite the same as “bag of inputs.“ Instead, they are “bag of key value pairs.” The input definitions have extra properties like `label`, `editor` and `defaultValue`.

---

## Bag of Inputs Approach

Let’s try see what happens if we try to redefine it using bag-of-inputs strategy.

```json
"inputs": {
  "template": {
    "type": "object",
    "fields": {
      "templateImage": { "type": "image", "label": null },
      "row": { "type": "number", "label": "Rows" },
      "col": { "type": "number", "label": "Columns" }
    },
    "defaultValue": "template1",
    "options": [{ 
      "label": "Template 1", 
      "id": "template1",
      "value": { 
        "templateImage": "template1.png",
        "numRows": 2,
        "numColumns": 4
      }
    }]
  }
}
```

This way, we can include things like “labels“ per each field in the hash.

Now, if we pick this approach, we need to re-define the Animation Sheet. It now looks like:

```json
"shots": {
  "type": {
    "type": "array",
    "items": {
      "type": "object",
      "fields": {
        "id": "string",
        "prompt": "string",
        "loop": "boolean"
      }
    }
  },
  … 
}
```

In this case, we are making `fields` be part of the `type` definition. We need to convert this.

```json
"shots": {
  "type": "array",
  "items": {
    "type": "object",
    "fields": {
      "id": { "type": "string", "label": "ID" },
      "prompt": { "type": "string", "label": "Prompt" },
      "loop": { "type": "boolean", "label": "Loop" },
    }
  },
  … 
}
```

This way, the “type” is a literal that supports “array“ and “object.“

The `"type": "array"` supports specifying of array `items` which are then just `Input`s.

---

## Final Pipeline

Here is final `pipeline.json`.

```json
{
  "id": "character-pipeline-v1",
  "name": "Generate Character",
  "type": "pipeline",
  "description": "This is SpriteDXʼs default pipeline for 2D character sprite sheet generation.",
  "stages": [
    {
      "id": "template",
      "name": "Template",
      "type": "input",
      "inputs": {
        "template": {
          "type": "object",
          "fields": {
            "templateImage": { "type": "image", "label": null },
            "row": { "type": "number", "label": "Rows" },
            "col": { "type": "number", "label": "Columns" }
          },
          "defaultValue": "template1",
          "options": [
            {
              "label": "Template 1",
              "id": "template1",
              "value": {
                "templateImage": "template1.png",
                "numRows": 2,
                "numColumns": 4
              }
            }
          ]
        }
      }
    },
    {
      "id": "generate",
      "name": "Prompt",
      "type": "runner",
      "statusMessage": "Generating Reference…",
      "runner": "ComfyUI",
      "workflowRef": "path/to/workflow.json",
      "inputs": {
        "templateImage": {
          "type": "image",
          "label": "Template",
          "source": "..template.template.templateImage",
          "mapTo": "17"
        },
        "prompt": {
          "type": "string",
          "defaultValue": "…",
          "label": null,
          "editor": { "type": "textarea" },
          "mapTo": "69.prompt"
        },
        "seed": {
          "type": "number",
          "label": "Seed",
          "defaultValue": 42,
          "mapTo": "69.prompt"
        }
      },
      "outputs": { "referenceImage": { "type": "image", "mapTo": "69" } }
    },
    {
      "id": "animate",
      "name": "Animation",
      "type": "runner",
      "runner": "ComfyUI",
      "workflowRef": "path/to/workflow.json",
      "statusMessage": "Animating Character…",
      "inputs": {
        "referenceImage": {
          "type": "image",
          "mapTo": "1",
          "source": "..generate.referenceImage"
        },
        "sceneDescription": {
          "type": "string",
          "label": "Scene",
          "defaultValue": "…",
          "editor": { "type": "textarea" },
          "enabled": "advanced"
        },
        "shots": {
          "type": "array",
          "items": {
            "type": "object",
            "fields": {
              "id": { "type": "string", "label": "ID" },
              "prompt": { "type": "string", "label": "Prompt" },
              "loop": { "type": "boolean", "label": "Loop" }
            }
          },
          "defaultValue": [
            { "id": "greet", "prompt": "...", "loop": false },
            { "id": "idle", "prompt": "...", "loop": true },
            { "id": "run", "prompt": "...", "loop": true }
          ],
          "editor": {
            "type": "table",
            "columns": [
              { "key": "id", "label": "ID" },
              { "key": "prompt", "label": "Prompt" },
              { "key": "loop", "label": "Loop" }
            ]
          },
          "enabled": "advanced"
        },
        "fullPrompt": {
          "type": "string",
          "computed": [
            {
              "type": "map",
              "args": ["shots"],
              "as": "shot",
              "return": "`[SHOT ${idx}] ${shot.prompt} \n\n`"
            },
            {
              "type": "reduce",
              "args": ["computed"],
              "as": ["acc", "curr"],
              "initialValue": "`${sceneDescription}\n\n`",
              "return": "`${acc}${curr}`"
            }
          ],
          "mapTo": "51.prompt"
        }
      },
      "outputs": { "animatedWEBP": { "type": "image", "mapTo": "21" } }
    },
    {
      "id": "editing",
      "name": "Editing",
      "type": "runner",
      "runner": "ComfyUI",
      "workflowRef": "path/to/workflow.json",
      "statusMessage": "Editing Shots…",
      "inputs": {},
      "outputs": {}
    },
    {
      "id": "postProcessing",
      "name": "Post-Processing",
      "type": "runner",
      "runner": "ComfyUI",
      "workflowRef": "path/to/workflow.json",
      "statusMessage": "Editing Shots…",
      "inputs": {},
      "outputs": {}
    }
  ]
}
```

---

## Next Steps

This seems powerful enough, what’s next? I need to build the rendering logic for nested inputs. In particular, I need to build a generic “object“ editor which basically prints list of inputs. Then build a “select box“ which will preset these values.

```plaintext
┌─────────────────────────────────┐
│ v Template                      │
│ ┌─────────────────────────────┐ │
│ │ Machi V1                  v │ │
│ ├─────────────────────────────┤ │
│ │ ┌─────────────────────────┐ │ │
│ │ │                         │ │ │
│ │ │      Image Preview      │ │ │
│ │ │                         │ │ │
│ │ └─────────────────────────┘ │ │
│ │  Row             [       2] │ │
│ │  Col             [       4] │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
```

I guess I know what to work on starting on Monday.

— Sprited Dev 🌱
