@openfairygui/functions 0.2.0-alpha.1 → 0.2.0-alpha.11
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 +4 -0
- package/dist/index.cjs +872 -282
- package/dist/index.d.cts +143 -79
- package/dist/index.d.ts +143 -79
- package/dist/index.js +869 -283
- package/dist/uam-transaction.cjs +79 -0
- package/dist/uam-transaction.d.cts +42 -0
- package/dist/uam-transaction.d.ts +42 -0
- package/dist/uam-transaction.js +78 -0
- package/package.json +66 -53
- package/src/atlas.ts +502 -146
- package/src/codegen.ts +104 -66
- package/src/index.ts +25 -1
- package/src/plugins/loader.ts +85 -0
- package/src/plugins/types.ts +47 -0
- package/src/publish.ts +499 -105
- package/src/restore.ts +12 -5
- package/src/shared-types.ts +1 -0
- package/src/uam-transaction.ts +69 -1
- package/src/utils.ts +28 -0
package/src/atlas.ts
CHANGED
|
@@ -2,9 +2,15 @@ import { GearType, TransitionActionType, type Component, type Document, type Dra
|
|
|
2
2
|
import { COMPAT_NODE_RECT_FLAGS, type CompatNodeRect } from './max-rects-compat.js';
|
|
3
3
|
import { MaxRectsPackerCompat } from './max-rects-packer-compat.js';
|
|
4
4
|
import type { ExtrasMap, HasOptionalSrc, HasOptionalUrl } from './shared-types.js';
|
|
5
|
-
import { createTransform } from './utils.js';
|
|
5
|
+
import { createTransform, parseTextureSetMode, type TextureSetMode } from './utils.js';
|
|
6
6
|
|
|
7
7
|
export interface AtlasOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Limit atlas generation to specific package names.
|
|
10
|
+
* When omitted, all packages are processed.
|
|
11
|
+
*/
|
|
12
|
+
packages?: string[];
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* Sharp module instance, injected by the caller.
|
|
10
16
|
* Required for actual image compositing and trimImage.
|
|
@@ -95,7 +101,7 @@ export interface AtlasOptions {
|
|
|
95
101
|
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
const ATLAS_DEFAULTS: Required<Omit<AtlasOptions, 'encoder' | 'basePath' | 'outputPath' | 'mkdir' | 'readFileRaw'>> = {
|
|
104
|
+
const ATLAS_DEFAULTS: Required<Omit<AtlasOptions, 'packages' | 'encoder' | 'basePath' | 'outputPath' | 'mkdir' | 'readFileRaw'>> = {
|
|
99
105
|
maxSize: 2048,
|
|
100
106
|
fast: true,
|
|
101
107
|
allowRotation: true,
|
|
@@ -220,8 +226,23 @@ interface AtlasCompositeInput {
|
|
|
220
226
|
top: number;
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
function getSelectedSkeletonDependencyImageIds(resources: PackageResource[]): Set<string> {
|
|
230
|
+
const imageIds = new Set<string>();
|
|
231
|
+
const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource] as const));
|
|
232
|
+
for (const resource of resources) {
|
|
233
|
+
if (!isSkeletonResource(resource)) continue;
|
|
234
|
+
for (const requiredId of resource.getRequireIds()) {
|
|
235
|
+
if (!requiredId) continue;
|
|
236
|
+
const required = resourcesById.get(requiredId);
|
|
237
|
+
if (required && isImageResource(required)) imageIds.add(requiredId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return imageIds;
|
|
241
|
+
}
|
|
242
|
+
|
|
223
243
|
interface AtlasEncoderPipeline {
|
|
224
244
|
ensureAlpha(): AtlasEncoderPipeline;
|
|
245
|
+
resize(width: number, height: number, options?: { fit?: 'fill' }): AtlasEncoderPipeline;
|
|
225
246
|
raw(): AtlasEncoderPipeline;
|
|
226
247
|
extract(options: { left: number; top: number; width: number; height: number }): AtlasEncoderPipeline;
|
|
227
248
|
toBuffer(options: { resolveWithObject: true }): Promise<AtlasEncoderResolvedBuffer>;
|
|
@@ -425,19 +446,25 @@ export function atlas(_options: AtlasOptions = {}): Transform {
|
|
|
425
446
|
const logger = doc.getLogger();
|
|
426
447
|
const encoder = options.encoder as AtlasEncoder | undefined;
|
|
427
448
|
const doTrim = options.trimImage && !!encoder && !!options.basePath;
|
|
449
|
+
const packageFilter = options.packages ? new Set(options.packages) : null;
|
|
428
450
|
|
|
429
451
|
for (const pkg of root.listPackages()) {
|
|
452
|
+
if (packageFilter && !packageFilter.has(pkg.getName())) continue;
|
|
430
453
|
// Respect publish-selected resources when publish() precomputes a merged branch view.
|
|
431
454
|
const selectedPublishIds = new Set(((pkg.getExtras() as PackageAtlasExtras | undefined) ?? {}).publishedResourceIds ?? []);
|
|
432
455
|
const allResources = selectedPublishIds.size > 0
|
|
433
456
|
? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId()))
|
|
434
457
|
: pkg.listResources();
|
|
458
|
+
const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
|
|
435
459
|
// Process resources in declaration order (matching editor behavior)
|
|
436
460
|
const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
|
|
437
461
|
const resourceOrder = new Map(orderedResources.map((resource, index) => [resource.getId(), index]));
|
|
438
462
|
const inputOrder = new Map(allResources.map((resource, index) => [resource.getId(), index]));
|
|
439
463
|
const orderedAllResources = sortResourcesByOrder(allResources, resourceOrder, inputOrder);
|
|
440
|
-
const hasPackable = allResources.some((resource) =>
|
|
464
|
+
const hasPackable = allResources.some((resource) => {
|
|
465
|
+
if (isImageResource(resource) && skeletonDependencyImageIds.has(resource.getId())) return false;
|
|
466
|
+
return isPackableResource(resource);
|
|
467
|
+
});
|
|
441
468
|
if (!hasPackable) continue;
|
|
442
469
|
|
|
443
470
|
// Collect packable items in declaration order
|
|
@@ -531,169 +558,81 @@ export function atlas(_options: AtlasOptions = {}): Transform {
|
|
|
531
558
|
if (isImageResource(res)) {
|
|
532
559
|
// Pack referenced images, plus explicitly exported standalone images.
|
|
533
560
|
const resId = res.getId();
|
|
534
|
-
if (
|
|
561
|
+
if (skeletonDependencyImageIds.has(resId)) continue;
|
|
562
|
+
if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
|
|
535
563
|
await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
|
|
536
564
|
} else if (isMovieClipResource(res)) {
|
|
537
565
|
const resId = res.getId();
|
|
538
|
-
if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
|
|
566
|
+
if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
|
|
539
567
|
await _collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
|
|
540
568
|
} else if (isFontResource(res)) {
|
|
541
569
|
const resId = res.getId();
|
|
542
|
-
if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
|
|
570
|
+
if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
|
|
543
571
|
await _collectFontTexture(doc, res, pkg, options);
|
|
544
572
|
}
|
|
545
573
|
}
|
|
546
574
|
|
|
547
575
|
if (inputs.length === 0) continue;
|
|
548
|
-
const branchGroups = buildBranchAtlasGroups(doc, inputs, options);
|
|
549
576
|
let totalPageCount = 0;
|
|
550
577
|
let usedDirectOutput = false;
|
|
578
|
+
const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
|
|
579
|
+
const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
|
|
580
|
+
const branchPageOffsets = new Map<number, number>();
|
|
551
581
|
|
|
552
582
|
for (const group of branchGroups) {
|
|
553
|
-
const directOutput =
|
|
583
|
+
const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0
|
|
584
|
+
? resolveDirectImageOutput(group.inputs, options)
|
|
585
|
+
: null;
|
|
554
586
|
if (directOutput) {
|
|
555
587
|
await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
|
|
556
588
|
usedDirectOutput = true;
|
|
557
589
|
totalPageCount += 1;
|
|
558
590
|
continue;
|
|
559
591
|
}
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
592
|
+
const pageStart = reserveAutoPageStart(branchPageOffsets, group.branchOrdinal, reservedPageIndexes);
|
|
593
|
+
const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
|
|
594
|
+
branchName: group.branchName,
|
|
595
|
+
branchOrdinal: group.branchOrdinal,
|
|
596
|
+
pageStart,
|
|
597
|
+
fileNameAt: (pageIndex) => resolveAtlasOutputFileName(pkg, pageIndex, group.branchName),
|
|
598
|
+
options,
|
|
599
|
+
encoder,
|
|
600
|
+
logger,
|
|
563
601
|
});
|
|
602
|
+
totalPageCount += emittedPageCount;
|
|
603
|
+
branchPageOffsets.set(group.branchOrdinal, pageStart + emittedPageCount);
|
|
604
|
+
}
|
|
564
605
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
fast: options.fast,
|
|
576
|
-
edgePadding: false,
|
|
577
|
-
duplicatePadding: hasDuplicatePadding,
|
|
578
|
-
multiPage: options.multiPage,
|
|
579
|
-
preserveInputOrderOnTie: options.preserveInputOrderOnTie,
|
|
606
|
+
for (const group of fixedPageGroups) {
|
|
607
|
+
const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
|
|
608
|
+
branchName: group.branchName,
|
|
609
|
+
branchOrdinal: group.branchOrdinal,
|
|
610
|
+
pageStart: group.pageIndex,
|
|
611
|
+
forceSinglePage: true,
|
|
612
|
+
fileNameAt: () => resolveAtlasOutputFileName(pkg, group.pageIndex, group.branchName),
|
|
613
|
+
options,
|
|
614
|
+
encoder,
|
|
615
|
+
logger,
|
|
580
616
|
});
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
totalPageCount += pages.length;
|
|
584
|
-
|
|
585
|
-
for (let p = 0; p < pages.length; p++) {
|
|
586
|
-
const page = pages[p];
|
|
587
|
-
const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, p)}`);
|
|
588
|
-
atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, p));
|
|
589
|
-
atlasNode.setFile(resolveAtlasOutputFileName(pkg, p, group.branchName));
|
|
590
|
-
atlasNode.setWidth(page.width);
|
|
591
|
-
atlasNode.setHeight(page.height);
|
|
592
|
-
pkg.addAtlas(atlasNode);
|
|
593
|
-
|
|
594
|
-
for (const pr of page.outputRects) {
|
|
595
|
-
const input = group.inputs[pr.index];
|
|
596
|
-
if (!input) continue;
|
|
597
|
-
const packedSize = resolvePackedRectSize(input, pr.width, pr.height, pr.rotated);
|
|
598
|
-
const rotated = pr.rotated;
|
|
599
|
-
const sprite = doc.createSprite();
|
|
600
|
-
sprite.setItemId(input.id);
|
|
601
|
-
sprite.setRectX(pr.x);
|
|
602
|
-
sprite.setRectY(pr.y);
|
|
603
|
-
sprite.setRectWidth(packedSize.width);
|
|
604
|
-
sprite.setRectHeight(packedSize.height);
|
|
605
|
-
sprite.setRotated(rotated);
|
|
606
|
-
sprite.setOffsetX(input.offsetX);
|
|
607
|
-
sprite.setOffsetY(input.offsetY);
|
|
608
|
-
sprite.setOriginalWidth(input.originalWidth);
|
|
609
|
-
sprite.setOriginalHeight(input.originalHeight);
|
|
610
|
-
sprite.setAtlas(atlasNode);
|
|
611
|
-
atlasNode.addSprite(sprite);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
for (const res of allResources) {
|
|
615
|
-
if (!isFontResource(res)) continue;
|
|
616
|
-
const fextras = res.getExtras() as FontResourceExtras;
|
|
617
|
-
const alias = fextras?._fontSpriteAlias;
|
|
618
|
-
if (!alias) continue;
|
|
619
|
-
const imgSprite = page.outputRects.find((result) => group.inputs[result.index]?.id === alias.textureId);
|
|
620
|
-
if (!imgSprite) continue;
|
|
621
|
-
const imgInput = group.inputs[imgSprite.index];
|
|
622
|
-
const fontSprite = doc.createSprite();
|
|
623
|
-
fontSprite.setItemId(alias.fontId);
|
|
624
|
-
fontSprite.setRectX(imgSprite.x);
|
|
625
|
-
fontSprite.setRectY(imgSprite.y);
|
|
626
|
-
fontSprite.setRectWidth(imgSprite.width);
|
|
627
|
-
fontSprite.setRectHeight(imgSprite.height);
|
|
628
|
-
fontSprite.setRotated(imgSprite.rotated);
|
|
629
|
-
if (imgInput) {
|
|
630
|
-
fontSprite.setOffsetX(imgInput.offsetX);
|
|
631
|
-
fontSprite.setOffsetY(imgInput.offsetY);
|
|
632
|
-
fontSprite.setOriginalWidth(imgInput.originalWidth);
|
|
633
|
-
fontSprite.setOriginalHeight(imgInput.originalHeight);
|
|
634
|
-
}
|
|
635
|
-
fontSprite.setAtlas(atlasNode);
|
|
636
|
-
atlasNode.addSprite(fontSprite);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
617
|
+
totalPageCount += emittedPageCount;
|
|
618
|
+
}
|
|
639
619
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const compositeInputs: Array<{ input: Uint8Array; left: number; top: number }> = [];
|
|
647
|
-
|
|
648
|
-
for (const pr of page.outputRects) {
|
|
649
|
-
const input = group.inputs[pr.index];
|
|
650
|
-
if (!input) continue;
|
|
651
|
-
if (pr.width <= 0 || pr.height <= 0 || input.width <= 0 || input.height <= 0) continue;
|
|
652
|
-
try {
|
|
653
|
-
let imgBuffer: Uint8Array;
|
|
654
|
-
|
|
655
|
-
if (input.trimBuffer) {
|
|
656
|
-
imgBuffer = input.trimBuffer;
|
|
657
|
-
if (imgBuffer.length === 0) continue;
|
|
658
|
-
} else {
|
|
659
|
-
if (!isImageResource(input.resource)) {
|
|
660
|
-
logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
const filePath = _resolveImagePath(input.resource, pkg, options.basePath!);
|
|
664
|
-
imgBuffer = await encoder(filePath).toBuffer();
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (pr.rotated) imgBuffer = await encoder(imgBuffer).rotate(270).toBuffer();
|
|
668
|
-
|
|
669
|
-
compositeInputs.push({
|
|
670
|
-
input: imgBuffer,
|
|
671
|
-
left: pr.x,
|
|
672
|
-
top: pr.y,
|
|
673
|
-
});
|
|
674
|
-
} catch {
|
|
675
|
-
logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
620
|
+
const standalonePageOffsets = new Map(branchPageOffsets);
|
|
621
|
+
for (const group of fixedPageGroups) {
|
|
622
|
+
const nextPageIndex = group.pageIndex + 1;
|
|
623
|
+
const current = standalonePageOffsets.get(group.branchOrdinal) ?? 0;
|
|
624
|
+
if (nextPageIndex > current) standalonePageOffsets.set(group.branchOrdinal, nextPageIndex);
|
|
625
|
+
}
|
|
678
626
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
},
|
|
689
|
-
})
|
|
690
|
-
.composite(compositeInputs)
|
|
691
|
-
.png()
|
|
692
|
-
.toFile(outputFile);
|
|
693
|
-
|
|
694
|
-
logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
627
|
+
for (const group of standaloneGroups) {
|
|
628
|
+
const emittedPageCount = await emitStandaloneAtlasGroup(doc, pkg, group, {
|
|
629
|
+
atlasIndexStart: standalonePageOffsets.get(group.branchOrdinal) ?? 0,
|
|
630
|
+
options,
|
|
631
|
+
encoder,
|
|
632
|
+
logger,
|
|
633
|
+
});
|
|
634
|
+
totalPageCount += emittedPageCount;
|
|
635
|
+
standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
|
|
697
636
|
}
|
|
698
637
|
|
|
699
638
|
if (usedDirectOutput) {
|
|
@@ -747,6 +686,251 @@ function buildBranchAtlasGroups(doc: Document, inputs: InputItem[], options: Atl
|
|
|
747
686
|
}));
|
|
748
687
|
}
|
|
749
688
|
|
|
689
|
+
function reserveAutoPageStart(
|
|
690
|
+
branchPageOffsets: Map<number, number>,
|
|
691
|
+
branchOrdinal: number,
|
|
692
|
+
reservedPageIndexes: Set<number>,
|
|
693
|
+
): number {
|
|
694
|
+
let pageIndex = branchPageOffsets.get(branchOrdinal) ?? 0;
|
|
695
|
+
while (branchOrdinal === 0 && reservedPageIndexes.has(pageIndex)) {
|
|
696
|
+
pageIndex += 1;
|
|
697
|
+
}
|
|
698
|
+
return pageIndex;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function emitPagedAtlasGroup(
|
|
702
|
+
doc: Document,
|
|
703
|
+
pkg: Package,
|
|
704
|
+
allResources: PackageResource[],
|
|
705
|
+
inputs: InputItem[],
|
|
706
|
+
context: {
|
|
707
|
+
branchName: string;
|
|
708
|
+
branchOrdinal: number;
|
|
709
|
+
pageStart: number;
|
|
710
|
+
fileNameAt: (pageIndex: number) => string;
|
|
711
|
+
options: AtlasOptions;
|
|
712
|
+
encoder: AtlasEncoder | undefined;
|
|
713
|
+
logger: ILogger;
|
|
714
|
+
forceSinglePage?: boolean;
|
|
715
|
+
},
|
|
716
|
+
): Promise<number> {
|
|
717
|
+
if (inputs.length === 0) return 0;
|
|
718
|
+
const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
|
|
719
|
+
if (pages.length === 0) return 0;
|
|
720
|
+
|
|
721
|
+
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
722
|
+
const page = pages[pageOffset];
|
|
723
|
+
const pageIndex = context.pageStart + pageOffset;
|
|
724
|
+
const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(context.branchOrdinal, pageIndex)}`);
|
|
725
|
+
atlasNode.setIndex(resolveAtlasIndex(context.branchOrdinal, pageIndex));
|
|
726
|
+
atlasNode.setFile(context.fileNameAt(pageIndex));
|
|
727
|
+
atlasNode.setWidth(page.width);
|
|
728
|
+
atlasNode.setHeight(page.height);
|
|
729
|
+
pkg.addAtlas(atlasNode);
|
|
730
|
+
|
|
731
|
+
attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
|
|
732
|
+
await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return pages.length;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async function emitStandaloneAtlasGroup(
|
|
739
|
+
doc: Document,
|
|
740
|
+
pkg: Package,
|
|
741
|
+
group: StandaloneAtlasGroup,
|
|
742
|
+
context: {
|
|
743
|
+
atlasIndexStart: number;
|
|
744
|
+
options: AtlasOptions;
|
|
745
|
+
encoder: AtlasEncoder | undefined;
|
|
746
|
+
logger: ILogger;
|
|
747
|
+
},
|
|
748
|
+
): Promise<number> {
|
|
749
|
+
if (group.inputs.length === 0) return 0;
|
|
750
|
+
const pages = packAtlasPages(
|
|
751
|
+
group.inputs,
|
|
752
|
+
context.options,
|
|
753
|
+
true,
|
|
754
|
+
group.sizeMode === 'npot'
|
|
755
|
+
? { powerOfTwo: false, multipleOfFour: false, square: false }
|
|
756
|
+
: group.sizeMode === 'multipleOf4'
|
|
757
|
+
? { powerOfTwo: false, multipleOfFour: true, square: false }
|
|
758
|
+
: undefined,
|
|
759
|
+
);
|
|
760
|
+
if (pages.length === 0) return 0;
|
|
761
|
+
|
|
762
|
+
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
763
|
+
const page = pages[pageOffset];
|
|
764
|
+
const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
|
|
765
|
+
const atlasFileName = pages.length <= 1 ? baseFileName : insertFileNameSuffix(baseFileName, `_${pageOffset}`);
|
|
766
|
+
const atlasIndex = context.atlasIndexStart + pageOffset;
|
|
767
|
+
const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, atlasIndex)}`);
|
|
768
|
+
atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, atlasIndex));
|
|
769
|
+
atlasNode.setFile(atlasFileName);
|
|
770
|
+
const standaloneSize = resolveStandaloneAtlasSize(page.width, page.height, group.sizeMode, context.options);
|
|
771
|
+
atlasNode.setWidth(standaloneSize.width);
|
|
772
|
+
atlasNode.setHeight(standaloneSize.height);
|
|
773
|
+
pkg.addAtlas(atlasNode);
|
|
774
|
+
|
|
775
|
+
attachSpritesToAtlas(doc, [], group.inputs, page.outputRects, atlasNode);
|
|
776
|
+
await writeAtlasPageImage(
|
|
777
|
+
pkg,
|
|
778
|
+
group.inputs,
|
|
779
|
+
{ ...page, width: standaloneSize.width, height: standaloneSize.height },
|
|
780
|
+
atlasFileName,
|
|
781
|
+
context.encoder,
|
|
782
|
+
context.options,
|
|
783
|
+
context.logger,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return pages.length;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function packAtlasPages(
|
|
791
|
+
inputs: InputItem[],
|
|
792
|
+
options: AtlasOptions,
|
|
793
|
+
forceSinglePage: boolean,
|
|
794
|
+
sizeOverrides?: {
|
|
795
|
+
powerOfTwo: boolean;
|
|
796
|
+
multipleOfFour: boolean;
|
|
797
|
+
square: boolean;
|
|
798
|
+
},
|
|
799
|
+
): ReturnType<MaxRectsPackerCompat['pack']> {
|
|
800
|
+
const hasDuplicatePadding = inputs.some((input) => {
|
|
801
|
+
return isImageResource(input.resource) && input.resource.getDuplicatePadding?.() === true;
|
|
802
|
+
});
|
|
803
|
+
const packer = new MaxRectsPackerCompat({
|
|
804
|
+
pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
|
|
805
|
+
mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
|
|
806
|
+
padding: options.padding,
|
|
807
|
+
rotation: options.allowRotation,
|
|
808
|
+
minWidth: 16,
|
|
809
|
+
minHeight: 16,
|
|
810
|
+
maxWidth: options.maxSize,
|
|
811
|
+
maxHeight: options.maxSize,
|
|
812
|
+
square: sizeOverrides?.square ?? options.square,
|
|
813
|
+
fast: options.fast,
|
|
814
|
+
edgePadding: false,
|
|
815
|
+
duplicatePadding: hasDuplicatePadding,
|
|
816
|
+
multiPage: forceSinglePage ? false : options.multiPage,
|
|
817
|
+
preserveInputOrderOnTie: options.preserveInputOrderOnTie,
|
|
818
|
+
});
|
|
819
|
+
return packer.pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function attachSpritesToAtlas(
|
|
823
|
+
doc: Document,
|
|
824
|
+
allResources: PackageResource[],
|
|
825
|
+
inputs: InputItem[],
|
|
826
|
+
outputRects: Array<{ index: number; x: number; y: number; width: number; height: number; rotated: boolean }>,
|
|
827
|
+
atlasNode: ReturnType<Document['createAtlas']>,
|
|
828
|
+
): void {
|
|
829
|
+
for (const packedRect of outputRects) {
|
|
830
|
+
const input = inputs[packedRect.index];
|
|
831
|
+
if (!input) continue;
|
|
832
|
+
const packedSize = resolvePackedRectSize(input, packedRect.width, packedRect.height, packedRect.rotated);
|
|
833
|
+
const sprite = doc.createSprite();
|
|
834
|
+
sprite.setItemId(input.id);
|
|
835
|
+
sprite.setRectX(packedRect.x);
|
|
836
|
+
sprite.setRectY(packedRect.y);
|
|
837
|
+
sprite.setRectWidth(packedSize.width);
|
|
838
|
+
sprite.setRectHeight(packedSize.height);
|
|
839
|
+
sprite.setRotated(packedRect.rotated);
|
|
840
|
+
sprite.setOffsetX(input.offsetX);
|
|
841
|
+
sprite.setOffsetY(input.offsetY);
|
|
842
|
+
sprite.setOriginalWidth(input.originalWidth);
|
|
843
|
+
sprite.setOriginalHeight(input.originalHeight);
|
|
844
|
+
sprite.setAtlas(atlasNode);
|
|
845
|
+
atlasNode.addSprite(sprite);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
for (const resource of allResources) {
|
|
849
|
+
if (!isFontResource(resource)) continue;
|
|
850
|
+
const extras = resource.getExtras() as FontResourceExtras;
|
|
851
|
+
const alias = extras?._fontSpriteAlias;
|
|
852
|
+
if (!alias) continue;
|
|
853
|
+
const imageSprite = outputRects.find((result) => inputs[result.index]?.id === alias.textureId);
|
|
854
|
+
if (!imageSprite) continue;
|
|
855
|
+
const imageInput = inputs[imageSprite.index];
|
|
856
|
+
const fontSprite = doc.createSprite();
|
|
857
|
+
fontSprite.setItemId(alias.fontId);
|
|
858
|
+
fontSprite.setRectX(imageSprite.x);
|
|
859
|
+
fontSprite.setRectY(imageSprite.y);
|
|
860
|
+
fontSprite.setRectWidth(imageSprite.width);
|
|
861
|
+
fontSprite.setRectHeight(imageSprite.height);
|
|
862
|
+
fontSprite.setRotated(imageSprite.rotated);
|
|
863
|
+
if (imageInput) {
|
|
864
|
+
fontSprite.setOffsetX(imageInput.offsetX);
|
|
865
|
+
fontSprite.setOffsetY(imageInput.offsetY);
|
|
866
|
+
fontSprite.setOriginalWidth(imageInput.originalWidth);
|
|
867
|
+
fontSprite.setOriginalHeight(imageInput.originalHeight);
|
|
868
|
+
}
|
|
869
|
+
fontSprite.setAtlas(atlasNode);
|
|
870
|
+
atlasNode.addSprite(fontSprite);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async function writeAtlasPageImage(
|
|
875
|
+
pkg: Package,
|
|
876
|
+
inputs: InputItem[],
|
|
877
|
+
page: { width: number; height: number; outputRects: Array<{ index: number; x: number; y: number; width: number; height: number; rotated: boolean }> },
|
|
878
|
+
atlasFileName: string,
|
|
879
|
+
encoder: AtlasEncoder | undefined,
|
|
880
|
+
options: AtlasOptions,
|
|
881
|
+
logger: ILogger,
|
|
882
|
+
): Promise<void> {
|
|
883
|
+
if (!encoder || !options.outputPath) return;
|
|
884
|
+
if (options.mkdir) {
|
|
885
|
+
await options.mkdir(options.outputPath);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const compositeInputs: Array<{ input: Uint8Array; left: number; top: number }> = [];
|
|
889
|
+
for (const packedRect of page.outputRects) {
|
|
890
|
+
const input = inputs[packedRect.index];
|
|
891
|
+
if (!input) continue;
|
|
892
|
+
if (packedRect.width <= 0 || packedRect.height <= 0 || input.width <= 0 || input.height <= 0) continue;
|
|
893
|
+
try {
|
|
894
|
+
let imageBuffer: Uint8Array;
|
|
895
|
+
if (input.trimBuffer) {
|
|
896
|
+
imageBuffer = input.trimBuffer;
|
|
897
|
+
if (imageBuffer.length === 0) continue;
|
|
898
|
+
} else if (input.rasterizedBuffer) {
|
|
899
|
+
imageBuffer = input.rasterizedBuffer;
|
|
900
|
+
} else {
|
|
901
|
+
if (!isImageResource(input.resource)) {
|
|
902
|
+
logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
const filePath = _resolveImagePath(input.resource, pkg, options.basePath!);
|
|
906
|
+
imageBuffer = await encoder(filePath).toBuffer();
|
|
907
|
+
}
|
|
908
|
+
if (packedRect.rotated) imageBuffer = await encoder(imageBuffer).rotate(270).toBuffer();
|
|
909
|
+
compositeInputs.push({
|
|
910
|
+
input: imageBuffer,
|
|
911
|
+
left: packedRect.x,
|
|
912
|
+
top: packedRect.y,
|
|
913
|
+
});
|
|
914
|
+
} catch {
|
|
915
|
+
logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const outputFile = `${options.outputPath}/${atlasFileName}`;
|
|
920
|
+
await encoder({
|
|
921
|
+
create: {
|
|
922
|
+
width: page.width,
|
|
923
|
+
height: page.height,
|
|
924
|
+
channels: 4 as const,
|
|
925
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
926
|
+
},
|
|
927
|
+
})
|
|
928
|
+
.composite(compositeInputs)
|
|
929
|
+
.toFile(outputFile);
|
|
930
|
+
|
|
931
|
+
logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
|
|
932
|
+
}
|
|
933
|
+
|
|
750
934
|
function inputToCompatRect(input: InputItem, index: number): CompatNodeRect {
|
|
751
935
|
const duplicatePadding = isImageResource(input.resource) && input.resource.getDuplicatePadding?.() === true;
|
|
752
936
|
return {
|
|
@@ -876,16 +1060,67 @@ function resolveAtlasOutputFileName(pkg: Package, pageIndex: number, branchName:
|
|
|
876
1060
|
return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
|
|
877
1061
|
}
|
|
878
1062
|
|
|
1063
|
+
function resolveStandaloneAtlasOutputFileName(
|
|
1064
|
+
pkg: Package,
|
|
1065
|
+
resource: PackInputResource,
|
|
1066
|
+
branchName: string,
|
|
1067
|
+
): string {
|
|
1068
|
+
const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
|
|
1069
|
+
const suffix = branchName ? `_${branchName}` : '';
|
|
1070
|
+
if (isImageResource(resource)) {
|
|
1071
|
+
const ext = extname(resolveImageFileName(resource)) || '.png';
|
|
1072
|
+
return `${baseName}${suffix}${ext}`;
|
|
1073
|
+
}
|
|
1074
|
+
return `${baseName}${suffix}.png`;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function resolveStandaloneAtlasSize(
|
|
1078
|
+
width: number,
|
|
1079
|
+
height: number,
|
|
1080
|
+
sizeMode: StandaloneAtlasGroup['sizeMode'],
|
|
1081
|
+
options: AtlasOptions,
|
|
1082
|
+
): { width: number; height: number } {
|
|
1083
|
+
if (sizeMode === 'npot') {
|
|
1084
|
+
return { width, height };
|
|
1085
|
+
}
|
|
1086
|
+
if (sizeMode === 'multipleOf4') {
|
|
1087
|
+
return {
|
|
1088
|
+
width: roundUpToMultiple(width, 4),
|
|
1089
|
+
height: roundUpToMultiple(height, 4),
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
return resolveDirectOutputAtlasSize(width, height, options);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
879
1095
|
function resolveImageFileName(resource: ImageResource): string {
|
|
880
1096
|
const extras = resource.getExtras() as ImageResourceExtras;
|
|
881
1097
|
return resource.getFileName() || extras._fileName || resource.getName();
|
|
882
1098
|
}
|
|
883
1099
|
|
|
1100
|
+
function extname(fileName: string): string {
|
|
1101
|
+
const normalized = fileName.replace(/\\/g, '/');
|
|
1102
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
1103
|
+
const lastDot = normalized.lastIndexOf('.');
|
|
1104
|
+
if (lastDot <= lastSlash) return '';
|
|
1105
|
+
return normalized.slice(lastDot);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function insertFileNameSuffix(fileName: string, suffix: string): string {
|
|
1109
|
+
const extension = extname(fileName);
|
|
1110
|
+
if (!extension) return `${fileName}${suffix}`;
|
|
1111
|
+
return `${fileName.slice(0, -extension.length)}${suffix}${extension}`;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
884
1114
|
function nextPow2(value: number): number {
|
|
885
1115
|
if (value <= 1) return 1;
|
|
886
1116
|
return 2 ** Math.ceil(Math.log2(value));
|
|
887
1117
|
}
|
|
888
1118
|
|
|
1119
|
+
function roundUpToMultiple(value: number, base: number): number {
|
|
1120
|
+
if (value <= 0) return 0;
|
|
1121
|
+
return Math.ceil(value / base) * base;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
889
1124
|
function sortResourcesByOrder(
|
|
890
1125
|
resources: PackageResource[],
|
|
891
1126
|
orderMap: Map<string, number>,
|
|
@@ -929,6 +1164,101 @@ interface ExtractedJtaData {
|
|
|
929
1164
|
meta?: ExtractedJtaMeta;
|
|
930
1165
|
}
|
|
931
1166
|
|
|
1167
|
+
function getResourceTextureSetMode(resource: PackInputResource): TextureSetMode {
|
|
1168
|
+
if (isImageResource(resource)) {
|
|
1169
|
+
return parseTextureSetMode(resource.getTextureSetMode?.());
|
|
1170
|
+
}
|
|
1171
|
+
return parseTextureSetMode(resource.getTextureSetMode?.());
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function groupStandaloneInputs(
|
|
1175
|
+
doc: Document,
|
|
1176
|
+
inputs: InputItem[],
|
|
1177
|
+
options: AtlasOptions,
|
|
1178
|
+
): {
|
|
1179
|
+
autoInputs: InputItem[];
|
|
1180
|
+
fixedPageGroups: PagedAtlasGroup[];
|
|
1181
|
+
standaloneGroups: StandaloneAtlasGroup[];
|
|
1182
|
+
reservedPageIndexes: Set<number>;
|
|
1183
|
+
} {
|
|
1184
|
+
const autoInputs: InputItem[] = [];
|
|
1185
|
+
const fixedInputsByPage = new Map<string, PagedAtlasGroup>();
|
|
1186
|
+
const standaloneGroups = new Map<string, StandaloneAtlasGroup>();
|
|
1187
|
+
const reservedPageIndexes = new Set<number>();
|
|
1188
|
+
const discoveredBranchNames = [...new Set(inputs
|
|
1189
|
+
.map((input) => getInputBranchName(input))
|
|
1190
|
+
.filter((branchName) => !!branchName))];
|
|
1191
|
+
const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
|
|
1192
|
+
for (const branchName of discoveredBranchNames) {
|
|
1193
|
+
if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
|
|
1194
|
+
}
|
|
1195
|
+
const branchOrdinalByName = new Map<string, number>();
|
|
1196
|
+
branchOrdinalByName.set('', 0);
|
|
1197
|
+
if (options.separatedAtlasForBranch) {
|
|
1198
|
+
let ordinal = 1;
|
|
1199
|
+
for (const branchName of orderedBranchNames) {
|
|
1200
|
+
branchOrdinalByName.set(branchName, ordinal++);
|
|
1201
|
+
}
|
|
1202
|
+
} else {
|
|
1203
|
+
for (const branchName of orderedBranchNames) {
|
|
1204
|
+
branchOrdinalByName.set(branchName, 0);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
for (const input of inputs) {
|
|
1209
|
+
const branchName = getInputBranchName(input);
|
|
1210
|
+
const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
|
|
1211
|
+
const mode = getResourceTextureSetMode(input.resource);
|
|
1212
|
+
if (mode.kind === 'standalone') {
|
|
1213
|
+
const resourceId = getPublishedItemId(input.resource);
|
|
1214
|
+
const key = `${branchName}\u0000${resourceId}`;
|
|
1215
|
+
const existing = standaloneGroups.get(key);
|
|
1216
|
+
if (existing) {
|
|
1217
|
+
existing.inputs.push(input);
|
|
1218
|
+
} else {
|
|
1219
|
+
standaloneGroups.set(key, {
|
|
1220
|
+
resource: input.resource,
|
|
1221
|
+
branchName,
|
|
1222
|
+
branchOrdinal,
|
|
1223
|
+
sizeMode: mode.sizeMode,
|
|
1224
|
+
inputs: [input],
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (mode.kind === 'page') {
|
|
1230
|
+
reservedPageIndexes.add(mode.pageIndex);
|
|
1231
|
+
const key = `${branchName}\u0000${mode.pageIndex}`;
|
|
1232
|
+
const existing = fixedInputsByPage.get(key);
|
|
1233
|
+
if (existing) {
|
|
1234
|
+
existing.inputs.push(input);
|
|
1235
|
+
} else {
|
|
1236
|
+
fixedInputsByPage.set(key, {
|
|
1237
|
+
pageIndex: mode.pageIndex,
|
|
1238
|
+
branchName,
|
|
1239
|
+
branchOrdinal,
|
|
1240
|
+
inputs: [input],
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
autoInputs.push(input);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return {
|
|
1249
|
+
autoInputs,
|
|
1250
|
+
fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) =>
|
|
1251
|
+
left.branchOrdinal - right.branchOrdinal
|
|
1252
|
+
|| left.pageIndex - right.pageIndex,
|
|
1253
|
+
),
|
|
1254
|
+
standaloneGroups: [...standaloneGroups.values()].sort((left, right) =>
|
|
1255
|
+
left.branchOrdinal - right.branchOrdinal
|
|
1256
|
+
|| getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource)),
|
|
1257
|
+
),
|
|
1258
|
+
reservedPageIndexes,
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
932
1262
|
/**
|
|
933
1263
|
* Trim transparent edges from an image using sharp.
|
|
934
1264
|
* Returns the trimmed buffer, dimensions, and offsets.
|
|
@@ -936,12 +1266,12 @@ interface ExtractedJtaData {
|
|
|
936
1266
|
*/
|
|
937
1267
|
async function _trimImage(
|
|
938
1268
|
encoder: AtlasEncoder,
|
|
939
|
-
|
|
1269
|
+
input: AtlasEncoderInput,
|
|
940
1270
|
originalWidth: number,
|
|
941
1271
|
originalHeight: number,
|
|
942
1272
|
): Promise<TrimInfo> {
|
|
943
1273
|
try {
|
|
944
|
-
const trimResult = await encoder(
|
|
1274
|
+
const trimResult = await encoder(input)
|
|
945
1275
|
.ensureAlpha()
|
|
946
1276
|
.raw()
|
|
947
1277
|
.toBuffer({ resolveWithObject: true });
|
|
@@ -982,7 +1312,7 @@ async function _trimImage(
|
|
|
982
1312
|
|
|
983
1313
|
const trimmedWidth = maxX - minX + 1;
|
|
984
1314
|
const trimmedHeight = maxY - minY + 1;
|
|
985
|
-
const buffer = await encoder(
|
|
1315
|
+
const buffer = await encoder(input)
|
|
986
1316
|
.extract({
|
|
987
1317
|
left: minX,
|
|
988
1318
|
top: minY,
|
|
@@ -1002,7 +1332,7 @@ async function _trimImage(
|
|
|
1002
1332
|
};
|
|
1003
1333
|
} catch {
|
|
1004
1334
|
// Trim failed (e.g. JPEG without alpha, nothing to trim) — return original
|
|
1005
|
-
const buf = await encoder(
|
|
1335
|
+
const buf = await encoder(input).png().toBuffer();
|
|
1006
1336
|
return {
|
|
1007
1337
|
buffer: buf,
|
|
1008
1338
|
width: originalWidth,
|
|
@@ -1035,10 +1365,25 @@ type InputItem = {
|
|
|
1035
1365
|
id: string; width: number; height: number;
|
|
1036
1366
|
originalWidth: number; originalHeight: number;
|
|
1037
1367
|
offsetX: number; offsetY: number;
|
|
1038
|
-
resource: PackInputResource; trimBuffer?: Uint8Array;
|
|
1368
|
+
resource: PackInputResource; trimBuffer?: Uint8Array; rasterizedBuffer?: Uint8Array;
|
|
1039
1369
|
sourceKind: 'image' | 'movieclip-frame';
|
|
1040
1370
|
};
|
|
1041
1371
|
|
|
1372
|
+
interface StandaloneAtlasGroup {
|
|
1373
|
+
resource: PackInputResource;
|
|
1374
|
+
branchName: string;
|
|
1375
|
+
branchOrdinal: number;
|
|
1376
|
+
sizeMode: 'default' | 'npot' | 'multipleOf4';
|
|
1377
|
+
inputs: InputItem[];
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
interface PagedAtlasGroup {
|
|
1381
|
+
pageIndex: number;
|
|
1382
|
+
branchName: string;
|
|
1383
|
+
branchOrdinal: number;
|
|
1384
|
+
inputs: InputItem[];
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1042
1387
|
/** Collect a single ImageResource into the inputs array. */
|
|
1043
1388
|
async function _collectImage(
|
|
1044
1389
|
resource: ImageResource,
|
|
@@ -1051,7 +1396,10 @@ async function _collectImage(
|
|
|
1051
1396
|
): Promise<void> {
|
|
1052
1397
|
let origW = resource.getWidth() ?? 0;
|
|
1053
1398
|
let origH = resource.getHeight() ?? 0;
|
|
1399
|
+
const declaredWidth = origW;
|
|
1400
|
+
const declaredHeight = origH;
|
|
1054
1401
|
let sourceHasAlpha = false;
|
|
1402
|
+
let rasterizedBuffer: Uint8Array | undefined;
|
|
1055
1403
|
|
|
1056
1404
|
if (encoder && options.basePath) {
|
|
1057
1405
|
const filePath = _resolveImagePath(resource, pkg, options.basePath);
|
|
@@ -1064,6 +1412,13 @@ async function _collectImage(
|
|
|
1064
1412
|
resource.setHeight(origH);
|
|
1065
1413
|
}
|
|
1066
1414
|
sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
|
|
1415
|
+
if (/\.svg$/i.test(resolveImageFileName(resource)) && declaredWidth > 0 && declaredHeight > 0) {
|
|
1416
|
+
rasterizedBuffer = await encoder(filePath)
|
|
1417
|
+
.resize(declaredWidth, declaredHeight, { fit: 'fill' })
|
|
1418
|
+
.png()
|
|
1419
|
+
.toBuffer();
|
|
1420
|
+
sourceHasAlpha = true;
|
|
1421
|
+
}
|
|
1067
1422
|
} catch {
|
|
1068
1423
|
if (origW === 0 || origH === 0) {
|
|
1069
1424
|
logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
|
|
@@ -1080,7 +1435,7 @@ async function _collectImage(
|
|
|
1080
1435
|
if (doTrim && sourceHasAlpha && options.basePath && encoder) {
|
|
1081
1436
|
const filePath = _resolveImagePath(resource, pkg, options.basePath);
|
|
1082
1437
|
try {
|
|
1083
|
-
const trimResult = await _trimImage(encoder, filePath, origW, origH);
|
|
1438
|
+
const trimResult = await _trimImage(encoder, rasterizedBuffer ?? filePath, origW, origH);
|
|
1084
1439
|
packW = trimResult.width;
|
|
1085
1440
|
packH = trimResult.height;
|
|
1086
1441
|
offX = trimResult.offsetX;
|
|
@@ -1097,6 +1452,7 @@ async function _collectImage(
|
|
|
1097
1452
|
offsetX: offX, offsetY: offY,
|
|
1098
1453
|
resource,
|
|
1099
1454
|
trimBuffer: trimBuf,
|
|
1455
|
+
rasterizedBuffer,
|
|
1100
1456
|
sourceKind: 'image',
|
|
1101
1457
|
});
|
|
1102
1458
|
}
|