pixelkiln 0.46.1 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,6 +54,7 @@ provenance and no long-lived npm token.
54
54
  | Generate and review | Resumable submit/poll/pick/fetch pipeline, exact next-step hints, candidate or atomic frame-set review, and a provenance gallery that can edit intent, generate under a budget, and compare records. |
55
55
  | Hand edits | Touch-ups in your own editor or a pinned in-browser Pixelorama, kept beside the generated art with the generation still the record; frame and tile sets member by member. |
56
56
  | Controlled inputs | Hashed image-to-image/inpaint lineage, fail-closed parent approval, source-versus-candidate review, and content-addressed per-asset ComfyUI bindings. |
57
+ | Characters | A base in 4 or 8 directions from a prompt or your own sprite (four PixelLab engines), poses and loops as dependent assets generated in waves, free mirrored directions, account adoption, and SpriteFrames export. |
57
58
  | Existing-art onboarding | Manifest scaffolding, exact-hash account adoption, and prompt recovery. |
58
59
  | Recovery | Safe stale-output replacement, validated caches, durable references, resumable paid jobs, and per-asset generation history with free restores. |
59
60
  | Shared-account safety | Cross-project claim files or a registered workspace catalog, sibling-style exclusion, reviewed salvage, keep/discard tags, separate confirmed purge. |
@@ -177,8 +178,8 @@ packaging layer.
177
178
  "styles": {
178
179
  "base": {
179
180
  "generator": "map",
180
- "promptPrefix": "Pixel-art game prop: ",
181
- "promptSuffix": ", isolated, transparent background",
181
+ "promptPrefix": "pixel-art game prop",
182
+ "promptSuffix": "isolated, transparent background",
182
183
  "outDir": "assets/generated/base",
183
184
  "tags": ["my-game"]
184
185
  }
@@ -390,9 +391,8 @@ package share one source. Policies: [Contributing](./CONTRIBUTING.md),
390
391
 
391
392
  ## Scope
392
393
 
393
- Salvaging characters, and PixelLab's portrait and outfit tools, are not
394
- implemented. Cross-project content-cache reuse and `workspace find`
395
- are deferred. See the open [roadmap issues](https://github.com/gfargo/pixelkiln/issues).
394
+ Salvaging characters, PixelLab's portrait and outfit tools, cross-project
395
+ cache reuse, and `workspace find` are not implemented; see the open [issues](https://github.com/gfargo/pixelkiln/issues).
396
396
 
397
397
  ## License
398
398
 
package/dist/cli.js CHANGED
@@ -5641,6 +5641,10 @@ function pixelLabObjectUrl(generator, objectId) {
5641
5641
  const setId = objectId.split("#")[0];
5642
5642
  return setId ? `https://www.pixellab.ai/maps/tiles/${encodeURIComponent(setId)}` : null;
5643
5643
  }
5644
+ if (generator === "character") {
5645
+ const characterId = objectId.split("#")[0];
5646
+ return characterId ? `https://www.pixellab.ai/create-character/${encodeURIComponent(characterId)}` : null;
5647
+ }
5644
5648
  return null;
5645
5649
  }
5646
5650
  function characterCost(spec) {
@@ -14597,15 +14601,27 @@ function packSprites(inputs, options = {}) {
14597
14601
  const sheetH = rows * cellH;
14598
14602
  const rgba = Buffer.alloc(sheetW * sheetH * 4);
14599
14603
  const frames = [];
14604
+ const pivot = options.pivot ?? "top-left";
14600
14605
  sprites.forEach((sprite, i) => {
14601
- const ox = i % columns * cellW;
14602
- const oy = Math.floor(i / columns) * cellH;
14606
+ const cellOx = i % columns * cellW;
14607
+ const cellOy = Math.floor(i / columns) * cellH;
14608
+ const offsetX = pivot === "bottom-center" ? Math.floor((cellW - sprite.width) / 2) : 0;
14609
+ const offsetY = pivot === "bottom-center" ? cellH - sprite.height : 0;
14610
+ const ox = cellOx + offsetX;
14611
+ const oy = cellOy + offsetY;
14603
14612
  for (let y = 0; y < sprite.height; y++) {
14604
14613
  const src = y * sprite.width * 4;
14605
14614
  const dst = ((oy + y) * sheetW + ox) * 4;
14606
14615
  sprite.pixels.copy(rgba, dst, src, src + sprite.width * 4);
14607
14616
  }
14608
- frames.push({ id: sprite.id, x: ox, y: oy, width: sprite.width, height: sprite.height });
14617
+ frames.push({
14618
+ id: sprite.id,
14619
+ x: ox,
14620
+ y: oy,
14621
+ width: sprite.width,
14622
+ height: sprite.height,
14623
+ ...offsetX || offsetY ? { offsetX, offsetY } : {}
14624
+ });
14609
14625
  });
14610
14626
  return {
14611
14627
  png: encodeRgbaPng(sheetW, sheetH, rgba),
@@ -15122,12 +15138,15 @@ function renderAsepriteSheet(atlas, opts) {
15122
15138
  }
15123
15139
  const frames = {};
15124
15140
  for (const frame of atlas.frames) {
15141
+ const offsetX = frame.offsetX ?? 0;
15142
+ const offsetY = frame.offsetY ?? 0;
15143
+ const pivoted = offsetX !== 0 || offsetY !== 0;
15125
15144
  frames[frame.id] = {
15126
15145
  frame: { x: frame.x, y: frame.y, w: frame.width, h: frame.height },
15127
15146
  rotated: false,
15128
- trimmed: false,
15129
- spriteSourceSize: { x: 0, y: 0, w: frame.width, h: frame.height },
15130
- sourceSize: { w: frame.width, h: frame.height },
15147
+ trimmed: pivoted,
15148
+ spriteSourceSize: pivoted ? { x: offsetX, y: offsetY, w: frame.width, h: frame.height } : { x: 0, y: 0, w: frame.width, h: frame.height },
15149
+ sourceSize: pivoted ? { w: atlas.cell.width, h: atlas.cell.height } : { w: frame.width, h: frame.height },
15131
15150
  duration: durations.get(frame.id) ?? ASEPRITE_DEFAULT_DURATION_MS
15132
15151
  };
15133
15152
  }
@@ -15232,16 +15251,18 @@ async function runPack(args) {
15232
15251
  const manifestDir = path38.dirname(path38.resolve(args.manifest));
15233
15252
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
15234
15253
  for (const styleId of styleIds) {
15254
+ const style = loaded.manifest.styles[styleId];
15255
+ const pivot = style?.generator === "character" ? "bottom-center" : "top-left";
15235
15256
  const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
15236
15257
  const qualitySources = await requireApprovedQualitySources(packagingSpecs, lock);
15237
15258
  const { png, atlas, skipped, sources } = packStyle(lock, styleId, manifestDir, {
15238
15259
  columns: args.columns,
15239
15260
  outputRoles: args.outputRoles,
15240
15261
  primaryOnly: args.primaryOnly,
15262
+ pivot,
15241
15263
  sourceOverrides: qualitySources,
15242
15264
  sources: manifestSources(loaded.manifest, styleId)
15243
15265
  });
15244
- const style = loaded.manifest.styles[styleId];
15245
15266
  const base = args.out ? path38.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path38.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
15246
15267
  const format = sheetFormat(args);
15247
15268
  const { extension, document } = renderSheetDocument(format, atlas, { imageName: path38.basename(`${base}.png`) });
@@ -15268,6 +15289,7 @@ async function runPack(args) {
15268
15289
  format,
15269
15290
  order: "id",
15270
15291
  outputRoles: [...args.outputRoles].sort(),
15292
+ pivot,
15271
15293
  primaryOnly: args.primaryOnly,
15272
15294
  style: styleId
15273
15295
  }