Compare commits

...

2 Commits

Author SHA1 Message Date
e417b7de17
Change to dynamic generation 2024-07-27 18:46:56 -05:00
d638bbd3e2
capitalization 2024-05-30 07:05:48 -05:00
81 changed files with 711 additions and 12 deletions

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
*.webm filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored
View File

@ -1 +1,2 @@
/dist /dist
/scripts/node_modules

View File

@ -13,7 +13,7 @@ Replaces the default bed textures with yiff. Supports 1.12+
* [lime](https://e621.net/posts/1976008) * [lime](https://e621.net/posts/1976008)
* [green](https://e621.net/posts/2416598) * [green](https://e621.net/posts/2416598)
* [cyan](https://e621.net/posts/2364029) * [cyan](https://e621.net/posts/2364029)
* [light_blue](https://e621.net/posts/2397890) * [light_blue](https://e621.net/posts/2194206)
* [blue](https://e621.net/posts/314664) * [blue](https://e621.net/posts/314664)
* [purple](https://e621.net/posts/1957635) * [purple](https://e621.net/posts/1957635)
* [magenta](https://e621.net/posts/1933688) * [magenta](https://e621.net/posts/1933688)
@ -38,7 +38,7 @@ Replaces the default bed textures with yiff. Supports 1.12+
| Lime | ![Lime](examples/lime.png) | [Source](https://e621.net/posts/1976008) | | Lime | ![Lime](examples/lime.png) | [Source](https://e621.net/posts/1976008) |
| Green | ![Green](examples/green.png) | [Source](https://e621.net/posts/2416598) | | Green | ![Green](examples/green.png) | [Source](https://e621.net/posts/2416598) |
| Cyan | ![Cyan](examples/cyan.png) | [Source](https://e621.net/posts/2364029) | | Cyan | ![Cyan](examples/cyan.png) | [Source](https://e621.net/posts/2364029) |
| Light Blue | ![Light Blue](examples/light_blue.png) | [Source](https://e621.net/posts/2397890) | | Light Blue | ![Light Blue](examples/light_blue.png) | [Source](https://e621.net/posts/2194206) |
| Blue | ![Blue](examples/blue.png) | [Source](https://e621.net/posts/314664) | | Blue | ![Blue](examples/blue.png) | [Source](https://e621.net/posts/314664) |
| Purple | ![Purple](examples/purple.png) | [Source](https://e621.net/posts/1957635) | | Purple | ![Purple](examples/purple.png) | [Source](https://e621.net/posts/1957635) |
| Magenta | ![Magenta](examples/magenta.png) | [Source](https://e621.net/posts/1933688) | | Magenta | ![Magenta](examples/magenta.png) | [Source](https://e621.net/posts/1933688) |

BIN
base.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

BIN
crop.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/black.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/blue.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/brown.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/cyan.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/gray.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/green.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/light_blue.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/light_gray.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/lime.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/magenta.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/orange.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/pink.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/purple.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/red.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/white.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/assets/minecraft/textures/entity/bed/yellow.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/pack.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

After

Width:  |  Height:  |  Size: 131 B

BIN
images/black.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/blue.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/brown.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/cyan.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/gray.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/green.png (Stored with Git LFS) Normal file

Binary file not shown.

91
images/images.json Normal file
View File

@ -0,0 +1,91 @@
{
"defaultSize": [512, 512],
"details": {
"top": {
"pos": [10, 117],
"size": [204, 59],
"cut": [0, 0]
},
"middle": {
"pos": [10, 224],
"size": [204, 128],
"cut": [0, 59]
},
"bottom": {
"pos": [176, 186],
"size": [128, 38],
"cut": [38, 187],
"flip": "horizontal"
}
},
"images": [
{
"name": "black",
"sizeMultiplier": 4
},
{
"name": "blue",
"sizeMultiplier": 4,
"rotate": -90
},
{
"name": "brown",
"sizeMultiplier": 4
},
{
"name": "cyan",
"sizeMultiplier": 4
},
{
"name": "gray",
"sizeMultiplier": 4
},
{
"name": "green",
"sizeMultiplier": 4
},
{
"name": "light_blue",
"sizeMultiplier": 4
},
{
"name": "light_gray",
"sizeMultiplier": 4
},
{
"name": "lime",
"sizeMultiplier": 4
},
{
"name": "magenta",
"sizeMultiplier": 4,
"rotate": -90,
"flip": "horizontal"
},
{
"name": "orange",
"sizeMultiplier": 4,
"rotate": -90
},
{
"name": "pink",
"sizeMultiplier": 4
},
{
"name": "purple",
"sizeMultiplier": 4
},
{
"name": "red",
"sizeMultiplier": 4
},
{
"name": "white",
"sizeMultiplier": 4
},
{
"name": "yellow",
"sizeMultiplier": 4
}
]
}

BIN
images/light_blue.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/light_gray.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/lime.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/magenta.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/orange.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/pink.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/purple.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/red.jpg (Stored with Git LFS) Normal file

Binary file not shown.

29
images/test.json Normal file
View File

@ -0,0 +1,29 @@
{
"defaultSize": [512, 512],
"details": {
"top": {
"pos": [10, 117],
"size": [204, 59],
"cut": [0, 0]
},
"middle": {
"pos": [10, 224],
"size": [204, 128],
"cut": [0, 59]
},
"bottom": {
"pos": [176, 186],
"size": [128, 38],
"cut": [38, 187],
"flip": "horizontal"
}
},
"images": [
{
"name": "animated"
},
{
"name": "orange"
}
]
}

BIN
images/white.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
images/yellow.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,21 +1,22 @@
{ {
"name": "yiffy Beds", "name": "Yiffy Beds",
"description": "Replaces bed textures with furry porn.", "description": "Replaces bed textures with furry porn.",
"url": "https://git.furry.cool/MCFurryPacks/YiffyBeds", "url": "https://git.furry.cool/MCFurryPacks/YiffyBeds",
"versions": [ "versions": [
["1.12", 3], ["1.12", 3],
["1.13", 4], ["1.13", 4],
["1.15", 5], ["1.15", 5],
["1.16", 6], ["1.16", 6],
["1.17", 7], ["1.17", 7],
["1.18", 8], ["1.18", 8],
["1.19", 9], ["1.19", 9],
["1.19.3", 12], ["1.19.3", 12],
["1.19.4", 13], ["1.19.4", 13],
["1.20", 15], ["1.20", 15],
["1.20.2", 18], ["1.20.2", 18],
["1.20.3", 26], ["1.20.3", 26],
["1.20.5", 32] ["1.20.5", 32],
["1.21", 34]
], ],
"exports": [] "exports": []
} }

8
scripts/.eslintrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": ["@uwu-codes/eslint-config/esm"],
"rules": {
"unicorn/prevent-abbreviations": "off",
"unicorn/no-process-exit": "off",
"unicorn/import-style": "off"
}
}

BIN
scripts/bun.lockb Executable file

Binary file not shown.

292
scripts/common.ts Normal file
View File

@ -0,0 +1,292 @@
import { randomBytes } from "crypto";
import { fileTypeFromBuffer } from "file-type";
import Ffmpeg from "fluent-ffmpeg";
import { rm } from "fs/promises";
import { readdir } from "fs/promises";
import { mkdtemp } from "fs/promises";
import { tmpdir } from "os";
import { basename } from "path";
import sharp, { type OverlayOptions, type Sharp } from "sharp";
import type { Config, Detail, Image } from "./images";
async function makeComposite(image: string | Buffer, details: Detail, sizeMultiplier: number, offset = 0) {
let img = await sharp(image)
.extract({
top: details.cut[1] * sizeMultiplier,
left: details.cut[0] * sizeMultiplier,
width: details.size[0] * sizeMultiplier,
height: details.size[1] * sizeMultiplier
}).toBuffer();
if (details.rotate) {
img = await sharp(img).rotate(details.rotate).toBuffer();
}
if (details.flip) {
img = await sharp(img).flip(details.flip === "horizontal").flop(details.flip === "vertical").toBuffer();
}
return {
input: img,
top: (details.pos[1] * sizeMultiplier) + offset,
left: details.pos[0] * sizeMultiplier
}
}
async function applyOptions(image: string | Buffer, options: Image, width: number, height: number) {
let img = await sharp(image).toBuffer()
if (options.rotate) {
img = await sharp(img).rotate(options.rotate).toBuffer();
}
if (options.flip) {
img = await sharp(img).flip(options.flip === "horizontal").flop(options.flip === "vertical").toBuffer();
}
img = await sharp(img).resize(width, height, { fit: "fill" }).toBuffer();
return img;
}
export async function formatImage(name: string, baseFile: string, config: Config, image: string | string[]) {
const options = config.images.find(img => img.name === name)!;
const isImage = !Array.isArray(image) && ["image/png", "image/jpeg"].includes((await fileTypeFromBuffer(Buffer.isBuffer(image) ? image : await Bun.file(image).arrayBuffer()))!.mime);
let result: Sharp;
const multplier = options.sizeMultiplier ?? 1;
const cropWidth = Math.max(config.details.top.size[0], config.details.middle.size[0], config.details.bottom.size[0]) * multplier,
cropHeight = (config.details.top.size[1] + config.details.middle.size[1] + config.details.bottom.size[1]) * multplier,
fullWidth = Math.floor(config.defaultSize[0] * multplier),
fullHeight = Math.floor(config.defaultSize[1] * multplier);
const base = sharp(baseFile);
if (multplier !== 1) {
base.resize(fullWidth, fullHeight, {
kernel: sharp.kernel.nearest
});
}
const baseBuf = await base.toBuffer();
if (isImage) {
const img = await applyOptions(image, options, cropWidth, cropHeight);
result = await sharp(baseBuf)
.composite([
await makeComposite(img, config.details.top, multplier),
await makeComposite(img, config.details.middle, multplier),
await makeComposite(img, config.details.bottom, multplier)
]);
} else {
let tmpDir: string | undefined;
let files: string[] = [];
tmpDir = await mkdtemp(`${tmpdir()}/split-frames-`);
if (!Array.isArray(image)) {
let select = "";
if (Array.isArray(options.frames)) {
select = options.frames.map(f => `eq(n\\,${f})`).join("+");
} else if (options.frames) {
select = `gte(n\\, ${options.frames.start})${options.frames.end ? `*lte(n\\,${options.frames.end}` : ""})`;
} else {
select = "n";
}
console.debug("Input file %s is not an image, assuming we need to extract frames.", name);
await new Promise<void>((resolve) => {
Ffmpeg(image)
.videoFilter(`scale=${cropWidth}:${cropHeight},select='${select}'`)
.addOption("-vsync vfr")
.output(`${tmpDir}/frame%04d.png`)
.on("end", async() => {
files = (await readdir(tmpDir!)).filter(f => /frame\d+\.png/.test(f)).sort().map(f => `${tmpDir}/${f}`);
if (files.length === 0) {
console.error("No frames extracted for %s.", name);
if (tmpDir) {
await rm(tmpDir, { recursive: true });
}
process.exit(1);
}
console.log("Frames extracted to %s, %d total for %s", tmpDir, files.length, name);
resolve();
})
.on("error", async(err) => {
if (tmpDir) {
await rm(tmpDir, { recursive: true });
}
console.error("Failed to extract frames for %s.", name);
console.error(err);
process.exit(1);
})
.run()
});
} else {
for (const img of image) {
await sharp(await applyOptions(img, options, cropWidth, cropHeight)).toFile(`${tmpDir}/${basename(img)}`);
}
files = image.map(f => `${tmpDir}/${basename(f)}`).sort();
}
const parts: OverlayOptions[] = [];
for (const img of files) {
const i = files.indexOf(img);
console.log("Processing %s (%d/%d) for %s", img, i + 1, files.length, name);
parts.push(
{ input: baseBuf, top: fullHeight * i, left: 0 },
await makeComposite(img, config.details.top, multplier, fullHeight * i),
await makeComposite(img, config.details.middle, multplier, fullHeight * i),
await makeComposite(img, config.details.bottom, multplier, fullHeight * i)
);
}
result = await sharp({
create: {
width: fullWidth,
height: fullHeight * files.length,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 }
},
limitInputPixels: false
})
.composite(parts)
if (tmpDir) {
await rm(tmpDir, { recursive: true });
}
}
return { isImage, result: result! };
}
const frameSizePercent = 0.1;
async function createFrame(colors: number[], percent: number, width: number, height: number) {
let frameW = width, frameH = height, i = 0;
const parts: OverlayOptions[] = [];
const frameVH = Math.floor(Math.min(width, height) * percent);
while (frameW > 0) {
if (i > colors.length - 1) {
i = 0;
}
const color = colors[i], r = (color >> 16) & 0xFF, g = (color >> 8) & 0xFF, b = color & 0xFF,
w = width * frameSizePercent, h = frameVH;
parts.push({
input: {
create: {
width: w,
height: h,
channels: 4,
background: { r, g, b, alpha: 255 }
}
},
top: 0,
// left to right
left: i * w
},
{
input: {
create: {
width: w,
height: h,
channels: 4,
background: { r, g, b, alpha: 255 }
}
},
top: frameH - h,
// right to left
left: width - (i + 1) * w
});
frameW -= w;
i += 1;
}
i = 0;
colors = colors.toReversed();
while (frameH > 0) {
if (i > colors.length - 1) {
i = 0;
}
const color = colors[i], r = (color >> 16) & 0xFF, g = (color >> 8) & 0xFF, b = color & 0xFF,
w = frameVH, h = height * frameSizePercent;
parts.push({
input: {
create: {
width: w,
height: h,
channels: 4,
background: { r, g, b, alpha: 255 }
}
},
top: i * h,
// top to bottom
left: 0
},
{
input: {
create: {
width: w,
height: h,
channels: 4,
background: { r, g, b, alpha: 255 }
}
},
top: height - (i + 1) * h,
// bottom to top
left: width - w
});
frameH -= h;
i += 1;
}
return sharp({
create: {
width,
height,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 }
},
limitInputPixels: false
}).composite(parts).png().toBuffer();
}
export const rgb = (hex: number) => ({ r: (hex >> 16) & 0xFF, g: (hex >> 8) & 0xFF, b: hex & 0xFF });
const kzBorderColor = rgb(0x6B3F7F)
const kzFillColor = rgb(0xD67FFF);
const plankSize = 4;
// the things I do to not have to make this a file
const plank = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH6AUfFCAKmCc95gAAATJJREFUKM9lUsFqwzAMlSXFXRYIYQRy6GX/tq/Zh/ZQGKUEOi9WpOyg1jWpL1Gen+X3/BS+vz6TCDxW2zQAoGZZ1et6JRF0UiSab1skUrMkklV9O4k4omZ+hv2TVfsuOC8SAQAh+paazbdtGu6/fLos9wrD4+a1aFht8+J0WZzAx4/Dq4ck0jZNEgEI820b+7sQQmRnF1TNXIyaubaxB5cKAKDK3rIdntTiyhtNQ2wrnM/XXGvd+Vlt25nkaYg19XzNfRdKGkV6yYFrxwAwDVHNPDhHXFWhcds0u05PiwCRaOyfD5BV+XzNq22urzjZZVLjew9VAlDdwz/z6kz0UfG3KsGVAStK+i74RDEhtojQiXetJ5QQk0gkotccAIBCWNQAlgPhosaYH1HI75+9v6F7+AdOcdbDy169/gAAAABJRU5ErkJggg==", "base64");
export async function makeKZ(squareSize: number, gridSize = 16) {
const kzBorderSize = squareSize * 0.0625;
const kzPlank = await sharp(plank).resize(squareSize, squareSize).toBuffer();
const plankStart = gridSize - plankSize;
const kzParts: OverlayOptions[] = [];
// i = top to bottom
// j = left to right
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (i < plankSize && j >= plankStart) {
kzParts.push({
input: kzPlank,
top: squareSize * i,
left: squareSize * j
});
} else {
kzParts.push({
input: {
create: {
width: kzBorderSize,
height: squareSize,
channels: 3,
background: kzBorderColor
}
},
top: squareSize * i,
left: squareSize * j
},{
input: {
create: {
width: squareSize,
height: kzBorderSize,
channels: 3,
background: kzBorderColor
}
},
top: squareSize * i,
left: squareSize * j
});
}
}
}
return sharp({
create: {
width: squareSize * 16,
height: squareSize * 16,
channels: 3,
background: kzFillColor
},
limitInputPixels: false
}).composite(kzParts).png().toBuffer();
}

117
scripts/images.ts Normal file
View File

@ -0,0 +1,117 @@
import type { PathLike } from "fs";
import { access } from "fs/promises";
import { dirname, resolve } from "path";
import sharp, { type OverlayOptions } from "sharp";
import { parseArgs } from "util";
import { formatImage, makeKZ } from "./common";
import { readFile } from "fs/promises";
import { writeFile } from "fs/promises";
import { rm } from "fs/promises";
import { mkdir } from "fs/promises";
import { stat } from "fs/promises";
import { readdir } from "fs/promises";
const { values: args } = parseArgs({
args: Bun.argv,
options: {
images: {
type: "string",
short: "i",
default: "images/images.json"
},
imagedir: {
type: "string",
short: "d",
default: "images"
},
outdir: {
type: "string",
short: "o",
default: "data"
},
basefile: {
type: "string",
short: "b",
default: "base.png"
},
throw: {
type: "boolean",
short: "t",
default: false
}
},
strict: true,
allowPositionals: true
});
const dirExists = async(path: PathLike) => access(path).then(() => true, () => false);
const imagesPath = resolve(args.images ?? "images.json");
const imageDir = resolve(args.imagedir ?? dirname(imagesPath));
const baseFile = resolve(args.basefile ?? "base.png");
const outDir = resolve(args.outdir ?? "data");
const throwOnMissing = !!args.throw;
if (!await dirExists(imageDir)) {
console.error("Image directory %s does not exist.", imageDir);
process.exit(1);
}
const ap = (p: string) => resolve(p, "assets/minecraft/textures/entity/bed");
// const ap = (p: string) => resolve(p, "../img");
await rm(`${outDir}/assets`, { recursive: true, force: true });
await mkdir(ap(outDir), { recursive: true });
export interface Image {
animation?: { frametime?: number; };
frames?: { start: number; end?: number; } | Array<number>;
name: string;
sizeMultiplier?: number;
rotate?: number;
flip?: "horizontal" | "vertical";
}
export interface Detail {
pos: [number, number];
size: [number, number];
cut: [number, number];
rotate?: number;
flip?: "horizontal" | "vertical";
}
export interface Details {
top: Detail;
middle: Detail;
bottom: Detail;
}
export interface Config {
defaultSize: [width: number, height: number];
details: Details;
images: Array<Image>;
}
const config = await Bun.file(imagesPath).json() as Config;
for (const image of config.images) {
let img: string | Array<string>;
if (await stat(`${imageDir}/${image.name}`).then(s => s.isDirectory(), () => false)) {
img = await readdir(`${imageDir}/${image.name}`).then(files => files.map(f => `${imageDir}/${image.name}/${f}`).sort());
} else {
const [file] = await Array.fromAsync(new Bun.Glob(`${image.name}.*`).scan({ onlyFiles: true, cwd: imageDir }));
if (!file) {
console.error("Image %s does not exist.", image.name);
if (throwOnMissing) {
process.exit(1);
}
continue;
}
img = `${imageDir}/${file}`
}
console.log("Processing %s", image.name);
const { isImage, result } = await formatImage(image.name, baseFile, config, img);
await result.toFile(`${ap(outDir)}/${image.name}.png`);
if (!isImage) {
await writeFile(`${ap(outDir)}/${image.name}.png.mcmeta`, JSON.stringify({ animation: image.animation ?? {} }));
}
}

26
scripts/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "image-maker",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^20.12.13",
"@uwu-codes/eslint-config": "^1.1.28",
"eslint": "^9.3.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"file-type": "^19.0.0",
"fluent-ffmpeg": "^2.1.3",
"sharp": "^0.33.4"
},
"scripts": {
"compile": "bun build ./images.ts --compile --outfile bin/make-images"
},
"bin": {
"make-images": "./images.ts"
}
}

22
scripts/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
}
}