pixelkiln 0.67.0 → 0.68.0

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/dist/cli.js CHANGED
@@ -689,6 +689,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
689
689
  uiPieces: spec.uiPieces,
690
690
  uiElements: spec.uiElements,
691
691
  uiColorPalette: spec.uiColorPalette,
692
+ // Scene and mask enter as their bytes' hashes, never their paths.
693
+ scene: spec.scene ? {
694
+ image: spec.scene.sha256,
695
+ placement: spec.scene.placement.type === "mask" ? { type: "mask", mask: spec.scene.placement.sha256 } : spec.scene.placement
696
+ } : void 0,
692
697
  styleTraits: spec.styleTraits,
693
698
  styleImages: styleImageHashes,
694
699
  // A character family: the engine and rotations of a base, the parent's
@@ -987,6 +992,16 @@ var CharacterPortraitSchema = z.object({
987
992
  of: z.string().min(1),
988
993
  size: CharacterPortraitSizeSchema.default(64)
989
994
  }).strict();
995
+ var MapScenePlacementSchema = z.union([
996
+ z.object({ oval: z.number().min(0.05).max(0.95) }).strict(),
997
+ z.object({ rectangle: z.number().min(0.05).max(0.95) }).strict(),
998
+ z.object({ mask: z.string().min(1) }).strict()
999
+ ]);
1000
+ var MapSceneSchema = z.object({
1001
+ /** Manifest-relative PNG of the scene. Any earlier asset's output file works. */
1002
+ image: z.string().min(1),
1003
+ placement: MapScenePlacementSchema.default({ oval: 0.3 })
1004
+ }).strict();
990
1005
  var CharacterOutfitSchema = z.object({
991
1006
  /** The loop (a character animation asset) to re-clothe, in the same style. */
992
1007
  of: z.string().min(1),
@@ -1681,6 +1696,8 @@ var AssetSchema = z.object({
1681
1696
  portrait: CharacterPortraitSchema.optional(),
1682
1697
  /** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
1683
1698
  outfit: CharacterOutfitSchema.optional(),
1699
+ /** `map` styles only: draw this object in the style of a scene image. See `MapSceneSchema`. */
1700
+ scene: MapSceneSchema.optional(),
1684
1701
  /** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
1685
1702
  proportions: CharacterProportionsSchema.optional(),
1686
1703
  /**
@@ -1757,7 +1774,7 @@ var AssetSchema = z.object({
1757
1774
  path: ["revision"]
1758
1775
  });
1759
1776
  }
1760
- const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit"].filter(Boolean);
1777
+ const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit", asset.scene && "scene"].filter(Boolean);
1761
1778
  if (shapes.length > 1) {
1762
1779
  context.addIssue({
1763
1780
  code: z.ZodIssueCode.custom,
@@ -4212,6 +4229,7 @@ async function inspectRevisionReadiness(spec, lock) {
4212
4229
  if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
4213
4230
  if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
4214
4231
  if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
4232
+ if (spec.scene) return inspectScene(spec);
4215
4233
  if (!spec.revision) return null;
4216
4234
  return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
4217
4235
  }
@@ -4229,6 +4247,16 @@ async function inspectStyleAnchor(spec, lock, seen) {
4229
4247
  }
4230
4248
  return { ready: true, reason: `${label} is current` };
4231
4249
  }
4250
+ async function inspectScene(spec) {
4251
+ const scene = spec.scene;
4252
+ const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
4253
+ if (scene.placement.type === "mask") files.push({ label: "scene mask", file: scene.placement.file, sha256: scene.placement.sha256 });
4254
+ for (const { label, file, sha256: sha2562 } of files) {
4255
+ if (!sha2562 || !existsSync4(file)) return { ready: false, reason: `${label} does not exist yet: ${file}` };
4256
+ if (await sha256File(file) !== sha2562) return { ready: false, reason: `${label} changed after the manifest was resolved` };
4257
+ }
4258
+ return { ready: true, reason: "scene is current" };
4259
+ }
4232
4260
  async function inspectMirror(spec, lock, seen) {
4233
4261
  const source = spec.mirror.sourceSpec;
4234
4262
  const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
@@ -6136,6 +6164,12 @@ var PixelLabClient = class {
6136
6164
  if (args.shading) body.shading = args.shading;
6137
6165
  if (args.detail) body.detail = args.detail;
6138
6166
  if (args.seed != null) body.seed = args.seed;
6167
+ if (args.scene) {
6168
+ const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6169
+ const placement = args.scene.placement;
6170
+ body.background_image = encode(args.scene.image);
6171
+ body.inpainting = placement.type === "mask" ? { type: "mask", mask_image: encode(placement.mask) } : { type: placement.type, fraction: placement.fraction };
6172
+ }
6139
6173
  return validateResponse(
6140
6174
  MapSubmitSchema,
6141
6175
  await this.request("/map-objects", { method: "POST", body: JSON.stringify(body) }),
@@ -7297,6 +7331,8 @@ var PixelLabProvider = class _PixelLabProvider {
7297
7331
  supports(generator) {
7298
7332
  return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement" || generator === "imageProFlash";
7299
7333
  }
7334
+ /** `/map-objects` style matching: `background_image` plus `inpainting`. */
7335
+ supportsMapScene = true;
7300
7336
  /**
7301
7337
  * `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
7302
7338
  * no mask) both exist on PixelLab, and so do `reduce-colors`
@@ -8442,7 +8478,8 @@ var PixelLabProvider = class _PixelLabProvider {
8442
8478
  outline: spec.outline,
8443
8479
  shading: spec.shading,
8444
8480
  detail: spec.detail,
8445
- seed: spec.seed
8481
+ seed: spec.seed,
8482
+ scene: spec.scene ? mapScenePayload(spec) : void 0
8446
8483
  });
8447
8484
  return { jobId: res.object_id, metadata: { backgroundJobId: res.background_job_id } };
8448
8485
  }
@@ -9319,6 +9356,21 @@ function findObjectAnimation(object, name, direction, animationId) {
9319
9356
  }
9320
9357
  return null;
9321
9358
  }
9359
+ function mapScenePayload(spec) {
9360
+ const scene = spec.scene;
9361
+ const read = (file, hash, label) => {
9362
+ const bytes = readFileSync2(file);
9363
+ if (!hash || sha256(bytes) !== hash) {
9364
+ throw new Error(`${spec.styleId}/${spec.assetId}: ${label} changed after the manifest was resolved`);
9365
+ }
9366
+ return { base64: bytes.toString("base64"), format: "png" };
9367
+ };
9368
+ const placement = scene.placement;
9369
+ return {
9370
+ image: read(scene.file, scene.sha256, "scene image"),
9371
+ placement: placement.type === "mask" ? { type: "mask", mask: read(placement.file, placement.sha256, "scene mask") } : placement
9372
+ };
9373
+ }
9322
9374
 
9323
9375
  // src/providers/retrodiffusion.ts
9324
9376
  var DEFAULT_BASE_URL2 = "https://api.retrodiffusion.ai/v1";
@@ -11021,6 +11073,7 @@ async function resolveSpecs(loaded, filter) {
11021
11073
  };
11022
11074
  }
11023
11075
  }
11076
+ if (asset.scene) resolved.scene = await resolveMapScene(root, styleId, assetId, resolved.generator, asset.scene, activeProvider);
11024
11077
  resolved.specHash = specHash(
11025
11078
  resolved,
11026
11079
  styleImageHashes,
@@ -11079,6 +11132,32 @@ function pngPath(file) {
11079
11132
  const extension = path13.extname(file);
11080
11133
  return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
11081
11134
  }
11135
+ async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
11136
+ if (generator !== "map") {
11137
+ throw new Error(`assets.${assetId}.scene: only a map object can be drawn into a scene; style "${styleId}" uses "${generator}"`);
11138
+ }
11139
+ if (!provider.supportsMapScene) {
11140
+ throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
11141
+ }
11142
+ const file = path13.resolve(root, scene.image);
11143
+ const image = await optionalRevisionImage(file, "scene image", "png");
11144
+ const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
11145
+ const placement = scene.placement;
11146
+ if ("mask" in placement) {
11147
+ const maskFile = path13.resolve(root, placement.mask);
11148
+ const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
11149
+ if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
11150
+ throw new Error(
11151
+ `assets.${assetId}.scene: the mask is ${mask.width}x${mask.height}; the scene ${scene.image} is ${image.width}x${image.height}`
11152
+ );
11153
+ }
11154
+ return {
11155
+ ...base,
11156
+ placement: { type: "mask", file: maskFile, sha256: mask?.hash ?? null, width: mask?.width ?? null, height: mask?.height ?? null }
11157
+ };
11158
+ }
11159
+ return "oval" in placement ? { ...base, placement: { type: "oval", fraction: placement.oval } } : { ...base, placement: { type: "rectangle", fraction: placement.rectangle } };
11160
+ }
11082
11161
  async function optionalRevisionImage(file, label, requiredFormat) {
11083
11162
  if (!existsSync8(file)) return null;
11084
11163
  const bytes = await readFile6(file);