pixelkiln 0.42.0 → 0.44.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 +311 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +394 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1414 -132
- package/dist/index.d.ts +1414 -132
- package/dist/index.js +384 -117
- package/dist/index.js.map +1 -1
- package/docs/GENERATORS.md +8 -4
- package/docs/MANIFEST.md +73 -3
- package/package.json +1 -1
- package/schema/manifest.schema.json +217 -0
- package/schema/recipe.schema.json +58 -0
- package/skills/pixelkiln/references/pixellab.md +10 -0
package/dist/cli.js
CHANGED
|
@@ -665,7 +665,18 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
665
665
|
parent: spec.character.parentAssetId,
|
|
666
666
|
parentSha256: spec.character.parentSha256 ?? null,
|
|
667
667
|
state: spec.character.state,
|
|
668
|
-
|
|
668
|
+
// Pose images enter as their hashes, never their paths.
|
|
669
|
+
animation: spec.character.animation ? {
|
|
670
|
+
...spec.character.animation,
|
|
671
|
+
startFrame: spec.character.animation.startFrame?.sha256,
|
|
672
|
+
endFrame: spec.character.animation.endFrame?.sha256
|
|
673
|
+
} : void 0,
|
|
674
|
+
// Absent keys keep the hashes of manifests that never set these.
|
|
675
|
+
proportions: spec.character.proportions,
|
|
676
|
+
textGuidanceScale: spec.character.textGuidanceScale,
|
|
677
|
+
isometric: spec.character.isometric,
|
|
678
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
679
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
|
|
669
680
|
} : void 0,
|
|
670
681
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
671
682
|
// compares those directly, so only the choice of source is identity.
|
|
@@ -783,6 +794,17 @@ var MIRRORED_DIRECTION = {
|
|
|
783
794
|
"south-west": "south-east"
|
|
784
795
|
};
|
|
785
796
|
var CharacterModeSchema = z.enum(["standard", "v3", "pro"]);
|
|
797
|
+
var CHARACTER_PROPORTION_PRESETS = ["default", "chibi", "cartoon", "stylized", "realistic_male", "realistic_female", "heroic"];
|
|
798
|
+
var CharacterProportionsSchema = z.union([
|
|
799
|
+
z.enum(CHARACTER_PROPORTION_PRESETS),
|
|
800
|
+
z.object({
|
|
801
|
+
headSize: z.number().min(0.5).max(2).optional(),
|
|
802
|
+
armsLength: z.number().min(0.5).max(2).optional(),
|
|
803
|
+
legsLength: z.number().min(0.5).max(2).optional(),
|
|
804
|
+
shoulderWidth: z.number().min(0.5).max(2).optional(),
|
|
805
|
+
hipWidth: z.number().min(0.5).max(2).optional()
|
|
806
|
+
}).strict()
|
|
807
|
+
]);
|
|
786
808
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
787
809
|
var CharacterStateSchema = z.object({
|
|
788
810
|
/** The base character, or another state, in the same style. */
|
|
@@ -809,11 +831,45 @@ var CharacterAnimationSchema = z.object({
|
|
|
809
831
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
810
832
|
keepFirstFrame: z.boolean().default(true),
|
|
811
833
|
/** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
|
|
812
|
-
mode: CharacterAnimationModeSchema.optional()
|
|
834
|
+
mode: CharacterAnimationModeSchema.optional(),
|
|
835
|
+
/**
|
|
836
|
+
* v3 only: a manifest-relative image of the pose to start from, instead
|
|
837
|
+
* of the character's rotation for this direction. Up to 256px.
|
|
838
|
+
*/
|
|
839
|
+
startFrame: z.string().min(1).optional(),
|
|
840
|
+
/**
|
|
841
|
+
* v3 only: a pose to animate toward. The loop then interpolates from the
|
|
842
|
+
* start frame (the rotation, or `startFrame`) to this image, which must
|
|
843
|
+
* be the same size as the start frame.
|
|
844
|
+
*/
|
|
845
|
+
endFrame: z.string().min(1).optional(),
|
|
846
|
+
/** What is being animated, when the character's own description would mislead the model. */
|
|
847
|
+
subject: z.string().min(1).optional(),
|
|
848
|
+
/** Template mode only: style hints for this loop, over the character's own. */
|
|
849
|
+
outline: z.string().min(1).optional(),
|
|
850
|
+
shading: z.string().min(1).optional(),
|
|
851
|
+
detail: z.string().min(1).optional(),
|
|
852
|
+
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
853
|
+
enhancePrompt: z.boolean().optional()
|
|
813
854
|
}).strict().superRefine((animation, context) => {
|
|
814
855
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
815
856
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
816
857
|
}
|
|
858
|
+
const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
|
|
859
|
+
for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
|
|
860
|
+
if (animation[key] !== void 0 && mode2 !== "v3") {
|
|
861
|
+
context.addIssue({
|
|
862
|
+
code: z.ZodIssueCode.custom,
|
|
863
|
+
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
|
|
864
|
+
path: [key]
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
for (const key of ["outline", "shading", "detail"]) {
|
|
869
|
+
if (animation[key] !== void 0 && mode2 !== "template") {
|
|
870
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
817
873
|
if (animation.template && animation.mode && animation.mode !== "template") {
|
|
818
874
|
context.addIssue({
|
|
819
875
|
code: z.ZodIssueCode.custom,
|
|
@@ -941,6 +997,18 @@ var StyleObjectSchema = z.object({
|
|
|
941
997
|
directions: z.union([z.literal(4), z.literal(8)]).optional(),
|
|
942
998
|
/** `character` only. Body template: `mannequin` (default) or a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`). */
|
|
943
999
|
template: z.string().min(1).optional(),
|
|
1000
|
+
/** `character` only, `standard` humanoid bases. A preset or multipliers; an asset may override it. */
|
|
1001
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
1002
|
+
/**
|
|
1003
|
+
* `character` only. How closely a `standard` base and a template loop
|
|
1004
|
+
* follow their text, 1 to 20; PixelLab's default is 8. The v3 and pro
|
|
1005
|
+
* engines do not take it.
|
|
1006
|
+
*/
|
|
1007
|
+
textGuidanceScale: z.number().min(1).max(20).optional(),
|
|
1008
|
+
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
1009
|
+
isometric: z.boolean().optional(),
|
|
1010
|
+
/** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
|
|
1011
|
+
enhancePrompt: z.boolean().optional(),
|
|
944
1012
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
945
1013
|
seed: z.number().int().optional(),
|
|
946
1014
|
/**
|
|
@@ -1100,6 +1168,19 @@ var AssetSchema = z.object({
|
|
|
1100
1168
|
state: CharacterStateSchema.optional(),
|
|
1101
1169
|
/** `character` styles: this asset is a loop of another character asset in one direction. */
|
|
1102
1170
|
animation: CharacterAnimationSchema.optional(),
|
|
1171
|
+
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1172
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
1173
|
+
/**
|
|
1174
|
+
* `character` styles, bases only: the character's own sprite, which
|
|
1175
|
+
* PixelLab rotates into the other directions instead of drawing from
|
|
1176
|
+
* the prompt. A manifest-relative PNG or JPEG of the south-facing
|
|
1177
|
+
* sprite, or an object keyed by direction (`{ "south": ..., "east":
|
|
1178
|
+
* ... }`); quadrupeds in `standard` mode need south and east, and only
|
|
1179
|
+
* `standard` takes more than south. `standard` wants each image at the
|
|
1180
|
+
* style's size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
1181
|
+
* still guides the result.
|
|
1182
|
+
*/
|
|
1183
|
+
reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
|
|
1103
1184
|
/**
|
|
1104
1185
|
* Another asset of the same style flipped left to right, made locally
|
|
1105
1186
|
* from that asset's downloaded files at no generation cost.
|
|
@@ -1174,6 +1255,16 @@ var AssetSchema = z.object({
|
|
|
1174
1255
|
path: ["prompt"]
|
|
1175
1256
|
});
|
|
1176
1257
|
}
|
|
1258
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
1259
|
+
context.addIssue({
|
|
1260
|
+
code: z.ZodIssueCode.custom,
|
|
1261
|
+
message: "a reference sprite belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
1262
|
+
path: ["reference"]
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
1266
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
1267
|
+
}
|
|
1177
1268
|
});
|
|
1178
1269
|
var DEFAULT_HISTORY_LIMIT = 5;
|
|
1179
1270
|
var HistoryLimitSchema = z.number().int().min(0).max(100);
|
|
@@ -1965,6 +2056,34 @@ function skipSubBlocks(bytes, start) {
|
|
|
1965
2056
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
1966
2057
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
1967
2058
|
}
|
|
2059
|
+
function imageMetadata(buf) {
|
|
2060
|
+
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
2061
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
2062
|
+
}
|
|
2063
|
+
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
2064
|
+
let offset = 2;
|
|
2065
|
+
while (offset + 3 < buf.length) {
|
|
2066
|
+
if (buf[offset] !== 255) return null;
|
|
2067
|
+
while (buf[offset] === 255) offset++;
|
|
2068
|
+
const marker = buf[offset++];
|
|
2069
|
+
if (marker == null || marker === 217 || marker === 218) break;
|
|
2070
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
2071
|
+
if (offset + 2 > buf.length) return null;
|
|
2072
|
+
const length = buf.readUInt16BE(offset);
|
|
2073
|
+
if (length < 2 || offset + length > buf.length) return null;
|
|
2074
|
+
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
2075
|
+
if (isStartOfFrame) {
|
|
2076
|
+
if (length < 7) return null;
|
|
2077
|
+
return {
|
|
2078
|
+
width: buf.readUInt16BE(offset + 5),
|
|
2079
|
+
height: buf.readUInt16BE(offset + 3),
|
|
2080
|
+
format: "jpeg"
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
offset += length;
|
|
2084
|
+
}
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
1968
2087
|
|
|
1969
2088
|
// src/outputs.ts
|
|
1970
2089
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -5016,6 +5135,17 @@ var PixelLabError = class extends ProviderError {
|
|
|
5016
5135
|
}
|
|
5017
5136
|
body;
|
|
5018
5137
|
};
|
|
5138
|
+
function proportionsPayload(proportions) {
|
|
5139
|
+
if (typeof proportions === "string") return { type: "preset", name: proportions };
|
|
5140
|
+
return {
|
|
5141
|
+
type: "custom",
|
|
5142
|
+
...proportions.headSize !== void 0 ? { head_size: proportions.headSize } : {},
|
|
5143
|
+
...proportions.armsLength !== void 0 ? { arms_length: proportions.armsLength } : {},
|
|
5144
|
+
...proportions.legsLength !== void 0 ? { legs_length: proportions.legsLength } : {},
|
|
5145
|
+
...proportions.shoulderWidth !== void 0 ? { shoulder_width: proportions.shoulderWidth } : {},
|
|
5146
|
+
...proportions.hipWidth !== void 0 ? { hip_width: proportions.hipWidth } : {}
|
|
5147
|
+
};
|
|
5148
|
+
}
|
|
5019
5149
|
var PixelLabClient = class {
|
|
5020
5150
|
constructor(apiKey, timeoutMs = 12e4) {
|
|
5021
5151
|
this.apiKey = apiKey;
|
|
@@ -5224,10 +5354,17 @@ var PixelLabClient = class {
|
|
|
5224
5354
|
* A base character. Standard mode picks the 4- or 8-direction template
|
|
5225
5355
|
* endpoint; v3 and pro have their own and always draw 8. Every one answers
|
|
5226
5356
|
* at once with the character id and a background job.
|
|
5357
|
+
*
|
|
5358
|
+
* A `reference` is the author's own sprite: standard takes one per
|
|
5359
|
+
* direction and draws the rest, v3 rotates the south one, and pro
|
|
5360
|
+
* switches to its rotate method for it. A `styleReference` is pro's style
|
|
5361
|
+
* anchor; the other engines have no such input.
|
|
5227
5362
|
*/
|
|
5228
5363
|
async createCharacter(args) {
|
|
5229
5364
|
const imageSize = { width: args.size, height: args.size };
|
|
5230
5365
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
5366
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
5367
|
+
const south = args.reference?.south;
|
|
5231
5368
|
let path45;
|
|
5232
5369
|
let body;
|
|
5233
5370
|
if (args.mode === "v3") {
|
|
@@ -5240,7 +5377,9 @@ var PixelLabClient = class {
|
|
|
5240
5377
|
...args.view ? { view: args.view } : {},
|
|
5241
5378
|
...args.outline ? { outline: args.outline } : {},
|
|
5242
5379
|
...args.detail ? { detail: args.detail } : {},
|
|
5243
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
5380
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
5381
|
+
...south ? { reference_image: encode(south) } : {},
|
|
5382
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
5244
5383
|
};
|
|
5245
5384
|
} else if (args.mode === "pro") {
|
|
5246
5385
|
path45 = "/create-character-pro";
|
|
@@ -5249,8 +5388,10 @@ var PixelLabClient = class {
|
|
|
5249
5388
|
image_size: imageSize,
|
|
5250
5389
|
template_id: args.template,
|
|
5251
5390
|
no_background: args.noBackground ?? true,
|
|
5391
|
+
method: south ? "rotate_character" : "create_with_style",
|
|
5252
5392
|
...args.view ? { view: args.view } : {},
|
|
5253
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
5393
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
5394
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
5254
5395
|
};
|
|
5255
5396
|
} else {
|
|
5256
5397
|
path45 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
@@ -5263,6 +5404,10 @@ var PixelLabClient = class {
|
|
|
5263
5404
|
...args.shading ? { shading: args.shading } : {},
|
|
5264
5405
|
...args.detail ? { detail: args.detail } : {},
|
|
5265
5406
|
...args.seed != null ? { seed: args.seed } : {},
|
|
5407
|
+
...args.proportions !== void 0 ? { proportions: proportionsPayload(args.proportions) } : {},
|
|
5408
|
+
...args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
5409
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
5410
|
+
...args.reference && Object.keys(args.reference).length ? { directions: Object.fromEntries(Object.entries(args.reference).flatMap(([direction, image]) => image ? [[direction, encode(image)]] : [])) } : {},
|
|
5266
5411
|
...palette
|
|
5267
5412
|
};
|
|
5268
5413
|
}
|
|
@@ -5291,6 +5436,7 @@ var PixelLabClient = class {
|
|
|
5291
5436
|
* the action text; pro is the sequential engine. One job per direction.
|
|
5292
5437
|
*/
|
|
5293
5438
|
async animateCharacter(args) {
|
|
5439
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
5294
5440
|
const raw = await this.request("/animate-character", {
|
|
5295
5441
|
method: "POST",
|
|
5296
5442
|
body: JSON.stringify({
|
|
@@ -5300,8 +5446,18 @@ var PixelLabClient = class {
|
|
|
5300
5446
|
directions: args.directions,
|
|
5301
5447
|
...args.template ? { template_animation_id: args.template } : {},
|
|
5302
5448
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
5449
|
+
...args.description ? { description: args.description } : {},
|
|
5303
5450
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
5304
5451
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
5452
|
+
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
5453
|
+
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
5454
|
+
...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
5455
|
+
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
5456
|
+
...args.mode === "template" && args.outline ? { outline: args.outline } : {},
|
|
5457
|
+
...args.mode === "template" && args.shading ? { shading: args.shading } : {},
|
|
5458
|
+
...args.mode === "template" && args.detail ? { detail: args.detail } : {},
|
|
5459
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
5460
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
5305
5461
|
...args.seed != null ? { seed: args.seed } : {}
|
|
5306
5462
|
})
|
|
5307
5463
|
});
|
|
@@ -5498,8 +5654,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5498
5654
|
"high detail"
|
|
5499
5655
|
]);
|
|
5500
5656
|
}
|
|
5501
|
-
if (spec.generator === "character") this.validateCharacter(spec);
|
|
5502
|
-
if ((spec.generator === "map" || spec.generator === "pixflux"
|
|
5657
|
+
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
5658
|
+
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
5503
5659
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
5504
5660
|
}
|
|
5505
5661
|
for (const image of styleImages) {
|
|
@@ -5515,9 +5671,80 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5515
5671
|
);
|
|
5516
5672
|
}
|
|
5517
5673
|
}
|
|
5518
|
-
validateCharacter(spec) {
|
|
5674
|
+
validateCharacter(spec, styleImages = []) {
|
|
5519
5675
|
const character = spec.character;
|
|
5520
5676
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
5677
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
5678
|
+
if (character.kind === "base" && character.proportions !== void 0) {
|
|
5679
|
+
if (character.mode !== "standard") {
|
|
5680
|
+
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
5681
|
+
}
|
|
5682
|
+
if (character.template !== "mannequin") {
|
|
5683
|
+
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
5687
|
+
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
5688
|
+
}
|
|
5689
|
+
if (character.animation) {
|
|
5690
|
+
const { startFrame, endFrame } = character.animation;
|
|
5691
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
5692
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
5693
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
if (endFrame) {
|
|
5697
|
+
const start = startFrame ?? (character.parentFile && existsSync7(character.parentFile) ? imageMetadata(readFileSync2(character.parentFile)) : null);
|
|
5698
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
5699
|
+
throw new Error(
|
|
5700
|
+
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "character's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
if (character.reference) {
|
|
5706
|
+
const given = Object.keys(character.reference);
|
|
5707
|
+
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
5708
|
+
if (character.mode !== "standard") {
|
|
5709
|
+
if (given.length > 1) throw new Error(`${label}: PixelLab ${character.mode} rotates one south-facing reference; drop the other directions`);
|
|
5710
|
+
const limit = character.mode === "v3" ? 256 : 168;
|
|
5711
|
+
const south = character.reference.south;
|
|
5712
|
+
if (south.width > limit || south.height > limit) {
|
|
5713
|
+
throw new Error(`${label}: reference is ${south.width}x${south.height}; PixelLab ${character.mode} takes up to ${limit}px`);
|
|
5714
|
+
}
|
|
5715
|
+
} else {
|
|
5716
|
+
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
5717
|
+
for (const direction of given) {
|
|
5718
|
+
if (!allowed.includes(direction)) {
|
|
5719
|
+
throw new Error(`${label}: reference "${direction}" is not one of this character's ${character.directions} directions`);
|
|
5720
|
+
}
|
|
5721
|
+
const image = character.reference[direction];
|
|
5722
|
+
if (image.width !== spec.width || image.height !== spec.height) {
|
|
5723
|
+
throw new Error(
|
|
5724
|
+
`${label}: reference (${direction}) is ${image.width}x${image.height}; standard mode wants each image at the style's size, ${spec.width}x${spec.height}`
|
|
5725
|
+
);
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
if (character.template !== "mannequin" && !character.reference.east) {
|
|
5729
|
+
throw new Error(`${label}: a ${character.template} reference needs south and east images`);
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
if (styleImages.length) {
|
|
5734
|
+
if (character.mode !== "pro") {
|
|
5735
|
+
throw new Error(
|
|
5736
|
+
`PixelLab ${character.mode} characters take no style images; give a base its own sprite with \`reference\`, or use mode pro for a style anchor`
|
|
5737
|
+
);
|
|
5738
|
+
}
|
|
5739
|
+
if (styleImages.length > 1) throw new Error("PixelLab pro characters take one style image");
|
|
5740
|
+
if (character.kind === "base" && character.reference) {
|
|
5741
|
+
throw new Error(`${label}: a pro base rotating its own reference takes no style image`);
|
|
5742
|
+
}
|
|
5743
|
+
const image = styleImages[0];
|
|
5744
|
+
if (image.width > 168 || image.height > 168) {
|
|
5745
|
+
throw new Error(`PixelLab pro character style image is ${image.width}x${image.height}; the limit is 168px`);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5521
5748
|
const maxSize = character.mode === "v3" ? 256 : 128;
|
|
5522
5749
|
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
5523
5750
|
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
@@ -5553,10 +5780,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5553
5780
|
* asset so a later run can find it, and clears its own earlier take for
|
|
5554
5781
|
* that direction first, since PixelLab skips a direction that exists.
|
|
5555
5782
|
*/
|
|
5556
|
-
async submitCharacter(spec, context) {
|
|
5783
|
+
async submitCharacter(spec, styleImages, context) {
|
|
5557
5784
|
const character = spec.character;
|
|
5558
5785
|
if (character.kind === "base") {
|
|
5559
5786
|
const swatch = spec.palette.length && character.mode === "standard" ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
5787
|
+
const styleImage = styleImages[0];
|
|
5560
5788
|
const res2 = await this.client.createCharacter({
|
|
5561
5789
|
mode: character.mode,
|
|
5562
5790
|
description: spec.prompt,
|
|
@@ -5569,7 +5797,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5569
5797
|
detail: spec.detail,
|
|
5570
5798
|
seed: spec.seed,
|
|
5571
5799
|
noBackground: spec.noBackground,
|
|
5572
|
-
paletteSwatchBase64: swatch
|
|
5800
|
+
paletteSwatchBase64: swatch,
|
|
5801
|
+
proportions: character.proportions,
|
|
5802
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
5803
|
+
isometric: character.isometric,
|
|
5804
|
+
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
5805
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
5806
|
+
enhancePrompt: character.enhancePrompt
|
|
5573
5807
|
});
|
|
5574
5808
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
5575
5809
|
}
|
|
@@ -5611,7 +5845,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5611
5845
|
frameCount: animation.frames,
|
|
5612
5846
|
keepFirstFrame: animation.keepFirstFrame,
|
|
5613
5847
|
directions: [animation.direction],
|
|
5614
|
-
seed: spec.seed
|
|
5848
|
+
seed: spec.seed,
|
|
5849
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
5850
|
+
isometric: character.isometric,
|
|
5851
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
5852
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
5853
|
+
description: animation.subject,
|
|
5854
|
+
outline: animation.outline,
|
|
5855
|
+
shading: animation.shading,
|
|
5856
|
+
detail: animation.detail,
|
|
5857
|
+
enhancePrompt: animation.enhancePrompt,
|
|
5858
|
+
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
5615
5859
|
});
|
|
5616
5860
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
5617
5861
|
return {
|
|
@@ -5701,7 +5945,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5701
5945
|
}
|
|
5702
5946
|
async submit(spec, styleImages, context) {
|
|
5703
5947
|
this.validate(spec, styleImages);
|
|
5704
|
-
if (spec.generator === "character") return this.submitCharacter(spec, context);
|
|
5948
|
+
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
5705
5949
|
if (spec.generator === "pixflux") {
|
|
5706
5950
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
5707
5951
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -5968,6 +6212,24 @@ function firstUrl(urls) {
|
|
|
5968
6212
|
if (!urls) return null;
|
|
5969
6213
|
return Object.values(urls).find((u) => typeof u === "string") ?? null;
|
|
5970
6214
|
}
|
|
6215
|
+
function readReference(spec, reference) {
|
|
6216
|
+
const images = {};
|
|
6217
|
+
for (const [direction, image] of Object.entries(reference)) {
|
|
6218
|
+
const bytes = readFileSync2(image.path);
|
|
6219
|
+
if (sha256(bytes) !== image.sha256) {
|
|
6220
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
6221
|
+
}
|
|
6222
|
+
images[direction] = { base64: bytes.toString("base64"), format: image.format };
|
|
6223
|
+
}
|
|
6224
|
+
return images;
|
|
6225
|
+
}
|
|
6226
|
+
function readPose(spec, what, image) {
|
|
6227
|
+
const bytes = readFileSync2(image.path);
|
|
6228
|
+
if (sha256(bytes) !== image.sha256) {
|
|
6229
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
6230
|
+
}
|
|
6231
|
+
return { base64: bytes.toString("base64"), format: image.format };
|
|
6232
|
+
}
|
|
5971
6233
|
function rotationSources(character) {
|
|
5972
6234
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
5973
6235
|
const sources = [];
|
|
@@ -7087,16 +7349,16 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7087
7349
|
}
|
|
7088
7350
|
}
|
|
7089
7351
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
7090
|
-
async function loadStyleImage(rel) {
|
|
7352
|
+
async function loadStyleImage(rel, what = "Style image") {
|
|
7091
7353
|
const abs = path13.resolve(root, rel);
|
|
7092
7354
|
let hit = styleImageCache.get(abs);
|
|
7093
7355
|
if (!hit) {
|
|
7094
|
-
if (!existsSync8(abs)) throw new Error(
|
|
7356
|
+
if (!existsSync8(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
7095
7357
|
const buf = await readFile6(abs);
|
|
7096
7358
|
const metadata = imageMetadata(buf);
|
|
7097
|
-
if (!metadata) throw new Error(
|
|
7359
|
+
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
7098
7360
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
7099
|
-
throw new Error(
|
|
7361
|
+
throw new Error(`${what} has invalid dimensions: ${abs}`);
|
|
7100
7362
|
}
|
|
7101
7363
|
hit = { base64: buf.toString("base64"), hash: sha256(buf), ...metadata };
|
|
7102
7364
|
styleImageCache.set(abs, hit);
|
|
@@ -7203,7 +7465,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7203
7465
|
tileView: generator === "tiles" ? style.tileView : void 0,
|
|
7204
7466
|
tileFeature: generator === "tiles" ? style.tileFeature : void 0,
|
|
7205
7467
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
7206
|
-
...generator === "character" ? { character: resolveCharacterShape(asset, style, characterKind) } : {},
|
|
7468
|
+
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7207
7469
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
7208
7470
|
costUnit: "generations",
|
|
7209
7471
|
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
@@ -7417,43 +7679,34 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
7417
7679
|
}
|
|
7418
7680
|
return out;
|
|
7419
7681
|
}
|
|
7420
|
-
function
|
|
7421
|
-
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
7422
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
7423
|
-
}
|
|
7424
|
-
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
7425
|
-
let offset = 2;
|
|
7426
|
-
while (offset + 3 < buf.length) {
|
|
7427
|
-
if (buf[offset] !== 255) return null;
|
|
7428
|
-
while (buf[offset] === 255) offset++;
|
|
7429
|
-
const marker = buf[offset++];
|
|
7430
|
-
if (marker == null || marker === 217 || marker === 218) break;
|
|
7431
|
-
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
7432
|
-
if (offset + 2 > buf.length) return null;
|
|
7433
|
-
const length = buf.readUInt16BE(offset);
|
|
7434
|
-
if (length < 2 || offset + length > buf.length) return null;
|
|
7435
|
-
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
7436
|
-
if (isStartOfFrame) {
|
|
7437
|
-
if (length < 7) return null;
|
|
7438
|
-
return {
|
|
7439
|
-
width: buf.readUInt16BE(offset + 5),
|
|
7440
|
-
height: buf.readUInt16BE(offset + 3),
|
|
7441
|
-
format: "jpeg"
|
|
7442
|
-
};
|
|
7443
|
-
}
|
|
7444
|
-
offset += length;
|
|
7445
|
-
}
|
|
7446
|
-
return null;
|
|
7447
|
-
}
|
|
7448
|
-
function resolveCharacterShape(asset, style, kind) {
|
|
7682
|
+
async function resolveCharacterShape(asset, style, kind, files) {
|
|
7449
7683
|
const mode2 = style.mode ?? "standard";
|
|
7450
7684
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
7685
|
+
const proportions = asset.proportions ?? style.proportions;
|
|
7451
7686
|
const shape = {
|
|
7452
7687
|
kind,
|
|
7453
7688
|
mode: mode2,
|
|
7454
7689
|
directions,
|
|
7455
|
-
template: style.template ?? "mannequin"
|
|
7690
|
+
template: style.template ?? "mannequin",
|
|
7691
|
+
...proportions !== void 0 ? { proportions } : {},
|
|
7692
|
+
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
7693
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
7694
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
7456
7695
|
};
|
|
7696
|
+
if (asset.reference) {
|
|
7697
|
+
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
7698
|
+
shape.reference = {};
|
|
7699
|
+
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
7700
|
+
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
7701
|
+
shape.reference[direction] = {
|
|
7702
|
+
path: path13.resolve(files.root, rel),
|
|
7703
|
+
sha256: image.hash,
|
|
7704
|
+
width: image.width,
|
|
7705
|
+
height: image.height,
|
|
7706
|
+
format: image.format
|
|
7707
|
+
};
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7457
7710
|
if (asset.state) {
|
|
7458
7711
|
shape.state = {
|
|
7459
7712
|
paletteFromReference: asset.state.paletteFromReference,
|
|
@@ -7462,13 +7715,24 @@ function resolveCharacterShape(asset, style, kind) {
|
|
|
7462
7715
|
}
|
|
7463
7716
|
if (asset.animation) {
|
|
7464
7717
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
7718
|
+
const pose = async (rel, what) => {
|
|
7719
|
+
const image = await files.load(rel, what);
|
|
7720
|
+
return { path: path13.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
7721
|
+
};
|
|
7465
7722
|
shape.animation = {
|
|
7466
7723
|
mode: animationMode,
|
|
7467
7724
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
7468
7725
|
direction: asset.animation.direction,
|
|
7469
7726
|
frames: asset.animation.frames ?? 8,
|
|
7470
7727
|
fps: asset.animation.fps,
|
|
7471
|
-
keepFirstFrame: asset.animation.keepFirstFrame
|
|
7728
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
7729
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
7730
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
7731
|
+
...asset.animation.subject ? { subject: asset.animation.subject } : {},
|
|
7732
|
+
...asset.animation.outline ? { outline: asset.animation.outline } : {},
|
|
7733
|
+
...asset.animation.shading ? { shading: asset.animation.shading } : {},
|
|
7734
|
+
...asset.animation.detail ? { detail: asset.animation.detail } : {},
|
|
7735
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
7472
7736
|
};
|
|
7473
7737
|
}
|
|
7474
7738
|
return shape;
|