pixelkiln 0.86.3 → 0.87.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/index.js CHANGED
@@ -35,17 +35,24 @@ function candidateCount(size) {
35
35
  }
36
36
  function generationCost(width, height, generator = "map") {
37
37
  if (generator === "map" || generator === "pixflux") return 1;
38
- if (generator === "imagePro") return 40;
38
+ if (generator === "imagePro") return proTierCost(width, height);
39
39
  const px = width * height;
40
- if (px <= 1024) return 20;
41
- if (px <= 2048) return 25;
42
- return 40;
40
+ if (px <= 1024) return PRO_TIER_PRICES[0];
41
+ if (px <= 2048) return PRO_TIER_PRICES[1];
42
+ return PRO_TIER_PRICES[2];
43
+ }
44
+ var PRO_TIER_PRICES = [10, 15, 25];
45
+ function proTierCost(width, height) {
46
+ const px = width * height;
47
+ if (px <= 65536) return PRO_TIER_PRICES[0];
48
+ if (px <= 102400) return PRO_TIER_PRICES[1];
49
+ return PRO_TIER_PRICES[2];
43
50
  }
44
51
  function tilesCost(tileSize, variations) {
45
52
  const px = tileSize * tileSize * Math.max(1, variations);
46
- if (px <= 1024) return 20;
47
- if (px <= 2048) return 25;
48
- return 40;
53
+ if (px <= 1024) return PRO_TIER_PRICES[0];
54
+ if (px <= 2048) return PRO_TIER_PRICES[1];
55
+ return PRO_TIER_PRICES[2];
49
56
  }
50
57
  function isometricTileCost() {
51
58
  return 1;
@@ -561,7 +568,7 @@ var MEMBER_SET_REVISION_MODES = ["reduce-colors", "correct-pixelart", "edit-anim
561
568
  var StyleObjectSchema = z.object({
562
569
  /** Generation backend for this style. Omit to inherit the manifest default. */
563
570
  provider: z.string().min(1).optional(),
564
- // `map` is the default because it is 20-40x cheaper and correct for any
571
+ // `map` is the default because it is 10-25x cheaper and correct for any
565
572
  // asset that is not going to be rotated or animated.
566
573
  generator: GeneratorSchema.default("map"),
567
574
  /** Square edge length. The active provider owns its exact limit. */
@@ -2384,7 +2391,8 @@ var PixelLabClient = class {
2384
2391
  * A full-body sprite in, a bust portrait out (or the reverse, unused
2385
2392
  * today). Job-based like a state or `1dir`, not the couple-generations
2386
2393
  * "conversion" the name suggests: the smallest `resultSize` (16px) billed
2387
- * 20 generations live (docs/ENDPOINTS.md, "Characters, measured").
2394
+ * 20 generations live (docs/ENDPOINTS.md, "Characters, measured"), the
2395
+ * Pro floor before PixelLab's October 2026 cut put it at 10.
2388
2396
  */
2389
2397
  async createPortraitCharacterPro(args) {
2390
2398
  const raw = await this.request("/portrait-character-pro", {
@@ -2423,7 +2431,8 @@ var PixelLabClient = class {
2423
2431
  * everything else here, but the completed job returns the result frames
2424
2432
  * inline as base64 (`last_response.quantized_images`), not as storage
2425
2433
  * URLs — the one PixelLab character tool that works this way. A live
2426
- * 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md).
2434
+ * 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md), before
2435
+ * PixelLab's October 2026 Pro cut halved that floor to 10.
2427
2436
  */
2428
2437
  async transferOutfitV2(args) {
2429
2438
  const image = (img) => ({
@@ -2473,7 +2482,9 @@ var PixelLabClient = class {
2473
2482
  * far higher than this tiering assumes; 352x352 (123904px²), 384x384
2474
2483
  * (147456px²), and 512x512 (262144px², the tiering ceiling) all billed
2475
2484
  * 40. Both breakpoints are now tightly bracketed: 20->25 in
2476
- * (65536px², 82944px²], 25->40 in (102400px², 123904px²].
2485
+ * (65536px², 82944px²], 25->40 in (102400px², 123904px²]. Those prices
2486
+ * predate PixelLab's October 2026 Pro cut to 10/15/25; `proTierCost`
2487
+ * keeps the breakpoints with the new prices.
2477
2488
  * `editImagesV2` below returns the same fields under `images`, plural
2478
2489
  * and array-wrapped, not `image` — do not assume the two endpoints share
2479
2490
  * one response shape. See docs/ENDPOINTS.md and docs/REVISIONS.md for
@@ -2526,8 +2537,9 @@ var PixelLabClient = class {
2526
2537
  );
2527
2538
  }
2528
2539
  /**
2529
- * PixelLab's Pro image tier, `/generate-image-v2`: a flat 40 generations
2530
- * (docs/ENDPOINTS.md, "Single-image generators, measured") for real style
2540
+ * PixelLab's Pro image tier, `/generate-image-v2`: 10 to 25 generations by
2541
+ * canvas since PixelLab's October 2026 Pro cut, a flat 40 before
2542
+ * (docs/ENDPOINTS.md, "Single-image generators, measured"), for real style
2531
2543
  * transfer and non-square canvases up to 792x688, where `pixflux` is
2532
2544
  * limited to 400x400 and no style reference. Like a revision, this hands
2533
2545
  * back a plain background job with no resource of its own; unlike a
@@ -2613,6 +2625,24 @@ var PixelLabClient = class {
2613
2625
  "inpaint-image-pro-flash"
2614
2626
  );
2615
2627
  }
2628
+ /**
2629
+ * `/image-to-pixelart-pro-flash`: a photo, painting, or render in, pixel
2630
+ * art out, with no size to pass: PixelLab detects the native pixel scale
2631
+ * and picks the output size (a 256x256 render came back 108x109, on a
2632
+ * transparent background). A flat 6 generations, billed exactly that live
2633
+ * in October 2026. Sources over 2048px on the longer side are scaled down
2634
+ * upstream. The finished job carries the image at `last_response.images[0]`.
2635
+ */
2636
+ async imageToPixelartProFlash(args) {
2637
+ const body = { image: args.image };
2638
+ if (args.description) body.description = args.description;
2639
+ if (args.seed != null) body.seed = args.seed;
2640
+ return validateResponse(
2641
+ ProFlashImageSubmitSchema,
2642
+ await this.request("/image-to-pixelart-pro-flash", { method: "POST", body: JSON.stringify(body) }),
2643
+ "image-to-pixelart-pro-flash"
2644
+ );
2645
+ }
2616
2646
  /**
2617
2647
  * `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
2618
2648
  * health bar, an inventory slot, a dialogue box) from a description,
@@ -3709,7 +3739,7 @@ function characterCost(spec) {
3709
3739
  }
3710
3740
  if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
3711
3741
  if (character.kind === "outfit") {
3712
- return 20;
3742
+ return PRO_TIER_PRICES[0];
3713
3743
  }
3714
3744
  if (character.mode === "standard") return 1;
3715
3745
  if (character.mode === "v3") {
@@ -3902,7 +3932,7 @@ var PixelLabProvider = class _PixelLabProvider {
3902
3932
  if (spec.revision) {
3903
3933
  const width = spec.revision.sourceWidth ?? spec.width;
3904
3934
  const height = spec.revision.sourceHeight ?? spec.height;
3905
- return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
3935
+ return { unit: "generations", amount: proTierCost(width, height), candidates: 1 };
3906
3936
  }
3907
3937
  if (spec.generator === "tiles" || spec.generator === "terrain" || spec.generator === "isometricTile") {
3908
3938
  return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
@@ -11945,7 +11975,7 @@ async function unzoomFile(client2, input, opts = {}) {
11945
11975
  }
11946
11976
  var FONT_WEIGHTS = ["Bold", "Regular"];
11947
11977
  var FONT_GLYPH_SIZES = [8, 16, 32, 64];
11948
- var FONT_COST_GENERATIONS = 25;
11978
+ var FONT_COST_GENERATIONS = 15;
11949
11979
  function fontOutputPaths(out) {
11950
11980
  const base = path17.resolve(out.replace(/\.(ttf|png)$/i, ""));
11951
11981
  return { ttf: `${base}.ttf`, atlas: `${base}.png` };
@@ -11994,6 +12024,61 @@ async function generateFont(client2, opts) {
11994
12024
  await sleep3(interval);
11995
12025
  }
11996
12026
  }
12027
+ var PIXELATE_COST_GENERATIONS = 6;
12028
+ var PIXELATE_MAX_SIDE = 2048;
12029
+ function defaultPixelateOut(input) {
12030
+ const ext = path17.extname(input);
12031
+ return `${ext ? input.slice(0, -ext.length) : input}.pixel.png`;
12032
+ }
12033
+ async function pixelateFile(client2, input, opts = {}) {
12034
+ const out = path17.resolve(opts.out ?? defaultPixelateOut(input));
12035
+ if (path17.resolve(input) === out) throw new Error("pixelate will not overwrite its own input; pass a different --out");
12036
+ if (existsSync11(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
12037
+ const bytes = await readFile9(input);
12038
+ const metadata = imageMetadata(bytes);
12039
+ if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
12040
+ const { width, height, format } = metadata;
12041
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
12042
+ const interval = opts.intervalMs ?? 5e3;
12043
+ const timeout = opts.timeoutMs ?? 10 * 6e4;
12044
+ const submitted = await client2.imageToPixelartProFlash({
12045
+ image: { base64: bytes.toString("base64"), format },
12046
+ description: opts.description,
12047
+ seed: opts.seed
12048
+ });
12049
+ const jobId = submitted.background_job_id;
12050
+ const started = Date.now();
12051
+ for (; ; ) {
12052
+ const job = await client2.getBackgroundJob(jobId);
12053
+ opts.onProgress?.(job.status);
12054
+ if (job.status === "failed") throw new Error(`PixelLab pixelate job ${jobId} failed upstream`);
12055
+ if (job.status === "completed") {
12056
+ const images = job.last_response?.images;
12057
+ const first = Array.isArray(images) ? images[0] : void 0;
12058
+ if (!first || typeof first.base64 !== "string" || !first.base64) {
12059
+ throw new Error(`PixelLab pixelate job ${jobId} completed without an image at last_response.images[0]`);
12060
+ }
12061
+ const png = Buffer.from(first.base64, "base64");
12062
+ const size = imageMetadata(png);
12063
+ await mkdir4(path17.dirname(out), { recursive: true });
12064
+ await writeFile4(out, png);
12065
+ return {
12066
+ jobId,
12067
+ out,
12068
+ original: { width, height },
12069
+ pixelated: { width: size?.width ?? Number(first.width), height: size?.height ?? Number(first.height) },
12070
+ imageId: submitted.image_id,
12071
+ usage: job.usage ?? null
12072
+ };
12073
+ }
12074
+ if (Date.now() - started > timeout) {
12075
+ throw new Error(
12076
+ `PixelLab pixelate job ${jobId} is still ${job.status} after ${Math.round(timeout / 1e3)} s; it keeps running upstream, but this command has stopped waiting`
12077
+ );
12078
+ }
12079
+ await sleep3(interval);
12080
+ }
12081
+ }
11997
12082
 
11998
12083
  // src/pipeline/quality-regression.ts
11999
12084
  import path18 from "path";
@@ -18969,8 +19054,11 @@ export {
18969
19054
  MapSceneSchema,
18970
19055
  MediaType,
18971
19056
  OverwriteRefusedError,
19057
+ PIXELATE_COST_GENERATIONS,
19058
+ PIXELATE_MAX_SIDE,
18972
19059
  PIXEL_ART_FIXER_REVISION,
18973
19060
  PIXEL_ART_FIXER_URL,
19061
+ PRO_TIER_PRICES,
18974
19062
  PixelKilnError,
18975
19063
  PixelLabClient,
18976
19064
  PixelLabError,
@@ -19026,6 +19114,7 @@ export {
19026
19114
  currentEntryOutputPath,
19027
19115
  currentOutputPath,
19028
19116
  defaultOpenCommand,
19117
+ defaultPixelateOut,
19029
19118
  defaultUnzoomOut,
19030
19119
  detachHandEdit,
19031
19120
  detectMediaType,
@@ -19108,11 +19197,13 @@ export {
19108
19197
  parseTerrainDescriptions,
19109
19198
  parseWorkspace,
19110
19199
  pickHistory,
19200
+ pixelateFile,
19111
19201
  pngSize,
19112
19202
  poll,
19113
19203
  portableOutputPath,
19114
19204
  prepareReview,
19115
19205
  primaryOutput,
19206
+ proTierCost,
19116
19207
  proportionsPayload,
19117
19208
  providerCredentialEnvs,
19118
19209
  providerFactory,