Compare commits

..

No commits in common. "master" and "419121c" have entirely different histories.

95 changed files with 161 additions and 495 deletions

106
README.md
View File

@ -1,118 +1,68 @@
# Yiffy Paintings
Replaces the default paintings with yiff. Supports 1.6+
### Sources
* [alban](https://e621.net/posts/1101130)
* [aztec](https://e621.net/posts/537087)
* [aztec2](https://e621.net/posts/426790)
* [bomb](https://e621.net/posts/1680202)
* [burning_skull](https://e621.net/posts/773268)
* [bust](https://e621.net/posts/2008022)
* [courbet](https://e621.net/posts/1746859)
* [creebet](https://e621.net/posts/406661)
* [donkey_kong](https://e621.net/posts/1375064)
* [fighters](https://e621.net/posts/1881510)
* [graham](https://e621.net/posts/1533304)
* [kebab](https://e621.net/posts/1571695)
* [match](https://e621.net/posts/1352699)
* [pigscene](https://e621.net/posts/1518055)
* [plant](https://e621.net/posts/1194572)
* [pointer](https://e621.net/pools/8368)
* [pool](https://e621.net/posts/854139)
* [sea](https://e621.net/posts/2939687)
* [skeleton](https://e621.net/posts/979894)
* [skulls\_and\_roses](https://e621.net/posts/1599707)
* [stage](https://e621.net/posts/314664)
* [sunset](https://e621.net/posts/2333033)
* [void](https://e621.net/posts/704825)
* [wanderer](https://e621.net/posts/956366)
* [wasteland](https://e621.net/posts/464478)
* [wither](https://e621.net/posts/1683783)
### Examples
<details>
<summary>1x1</summary>
* [kebab](https://e621.net/posts/1571695)
* [aztec](https://e621.net/posts/537087)
* [alban](https://e621.net/posts/1101130)
* [aztec2](https://e621.net/posts/426790)
* [bomb](https://e621.net/posts/1680202)
* [plant](https://e621.net/posts/1194572)
* [wasteland](https://e621.net/posts/464478)
* [meditative](https://e621.net/posts/3355209) (1.21+)
<img src="examples/1x1.png" width="80%">
</details>
<details>
<summary>1x2</summary>
* [wanderer](https://e621.net/posts/956366)
* [graham](https://e621.net/posts/1533304)
* [prairie_ride](https://e621.net/posts/4005374) (1.21+)
<img src="examples/1x2.png" width="80%">
</details>
<details>
<summary>2x1</summary>
* [pool](https://e621.net/posts/854139)
* [courbet](https://e621.net/posts/1746859)
* [sunset](https://e621.net/posts/2333033)
* [sea](https://e621.net/posts/2939687)
* [creebet](https://e621.net/posts/406661)
<img src="examples/2x1.png" width="80%">
</details>
<details>
<summary>2x2</summary>
* [match](https://e621.net/posts/1352699)
* [bust](https://e621.net/posts/2008022)
* [stage](https://e621.net/posts/314664)
* [void](https://e621.net/posts/704825)
* [skull\_and\_roses](https://e621.net/posts/1599707)
* [wither](https://e621.net/posts/1683783)
* [baroque](https://e621.net/posts/4524310) (1.21+)
* [humble](https://e621.net/posts/3293255) (1.21+)
* [earth](https://e621.net/posts/4394369) (1.19+)
* [wind](https://e621.net/posts/4025061) (1.19+)
* [fire](https://e621.net/posts/2984756) (1.19+)
* [water](https://e621.net/posts/2827630) (1.19+)
<img src="examples/2x2.png" width="80%">
</details>
<details>
<summary>3x3</summary>
* [bouquet](https://e621.net/posts/4420140) (1.21+)
* [cavebird](https://e621.net/posts/2433070) (1.21+)
* [cotan](https://e621.net/posts/2255060) (1.21+)
* [endboss](https://e621.net/posts/4543670) (1.21+)
* [fern](https://e621.net/posts/1339799) (1.21+)
* [owlemons](https://e621.net/posts/4623032) (1.21+)
* [sunflowers](https://e621.net/posts/4626501) (1.21+)
* [tides](https://e621.net/posts/4642340) (1.21+)
<img src="examples/3x3.png" width="80%">
</details>
<details>
<summary>3x4</summary>
* [backyard](https://e621.net/posts/4155856) (1.21+)
* [pond](https://e621.net/posts/3935565) (1.21+)
<img src="examples/3x4.png" width="80%">
</details>
<details>
<summary>4x2</summary>
* [fighters](https://e621.net/posts/1881510)
* [changing](https://e621.net/posts/3317590) (1.21+)
* [finding](https://e621.net/posts/1818640) (1.21+)
* [lowmist](https://e621.net/posts/1345861) (1.21+)
* [passage](https://e621.net/posts/4152001) (1.21+)
<img src="examples/4x2.png" width="80%">
</details>
<details>
<summary>4x3</summary>
* [skeleton](https://e621.net/posts/979894)
* [donkey_kong](https://e621.net/posts/1375064)
<img src="examples/4x3.png" width="80%">
</details>
<details>
<summary>4x4</summary>
* [burning_skull](https://e621.net/posts/773268)
* [pigscene](https://e621.net/posts/1518055)
* [pointer](https://e621.net/pools/8368)
* [unpacked](https://e621.net/posts/3543944) (1.21+)
* [orb](https://e621.net/posts/2626566) (1.21+)
<img src="examples/4x4.png" width="80%">
</details>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
{"animation":{"frametime":3}}

Binary file not shown.

View File

@ -1 +0,0 @@
{"animation":{}}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
{"animation":{}}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
examples/1x1.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/1x2.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/2x1.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/2x2.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/3x3.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/3x4.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/4x2.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/4x3.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/4x4.png (Stored with Git LFS)

Binary file not shown.

BIN
images/backyard.webm (Stored with Git LFS)

Binary file not shown.

BIN
images/baroque.webm (Stored with Git LFS)

Binary file not shown.

BIN
images/bouquet.png (Stored with Git LFS)

Binary file not shown.

BIN
images/cavebird.jpg (Stored with Git LFS)

Binary file not shown.

BIN
images/changing.png (Stored with Git LFS)

Binary file not shown.

BIN
images/cotan.png (Stored with Git LFS)

Binary file not shown.

BIN
images/earth.png (Stored with Git LFS)

Binary file not shown.

BIN
images/endboss.png (Stored with Git LFS)

Binary file not shown.

BIN
images/fern.jpg (Stored with Git LFS)

Binary file not shown.

BIN
images/finding.jpg (Stored with Git LFS)

Binary file not shown.

BIN
images/fire.png (Stored with Git LFS)

Binary file not shown.

BIN
images/humble.png (Stored with Git LFS)

Binary file not shown.

View File

@ -1,14 +1,14 @@
{
"sizes": {
"1x1": [320, 320],
"1x2": [320, 640],
"2x1": [640, 320],
"2x2": [640, 640],
"3x3": [960, 960],
"3x4": [960, 1280],
"4x2": [1280, 640],
"4x3": [1280, 960],
"4x4": [1280, 1280]
"1x1": [640, 640],
"1x2": [640, 1280],
"2x1": [1280, 640],
"2x2": [1280, 1280],
"3x3": [1920, 1920],
"3x4": [1920, 2560],
"4x2": [2560, 1280],
"4x3": [2560, 1920],
"4x4": [2560, 2560]
},
"images": [
{
@ -48,7 +48,7 @@
"kz": [6, 0]
},
{
"name": "meditative",
"name": "meditation",
"size": "1x1",
"kz": null
},
@ -81,22 +81,21 @@
"name": "wanderer",
"size": "1x2",
"kz": [0, 4],
"resize": [320, 320],
"resize": [640, 640],
"_comment": "For some reason this needs to be 1:1"
},
{
"name": "graham",
"size": "1x2",
"kz": [1, 4],
"resize": [320, 320],
"frames": [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58],
"resize": [640, 640],
"_comment": "For some reason this needs to be 1:1"
},
{
"name": "prairie_ride",
"size": "1x2",
"kz": null,
"resize": [320, 320],
"resize": [640, 640],
"_comment": "For some reason this needs to be 1:1"
},
{
@ -157,9 +156,7 @@
{
"name": "baroque",
"size": "2x2",
"kz": null,
"frames": { "start": 60, "end": 140 },
"resize": [320, 320]
"kz": null
},
{
"name": "humble",
@ -190,7 +187,7 @@
"name": "pointer",
"size": "4x4",
"kz": [0, 12],
"resize": [320, 320],
"resize": [640, 640],
"animation": { "frametime": 100 }
},
{
@ -202,8 +199,8 @@
"name": "burning_skull",
"size": "4x4",
"kz": [8, 12],
"resize": [320, 320],
"frames": { "start": 2805, "end": 2826 }
"resize": [640, 640],
"frames": [2805, 2826]
},
{
"name": "unpacked",
@ -268,11 +265,7 @@
{
"name": "backyard",
"size": "3x4",
"kz": null,
"resize": [640, 640],
"frames": [3, 6, 12, 15, 18],
"animation": { "frametime": 3 },
"_comment": "For some reason this needs to be 1:1"
"kz": null
},
{
"name": "pond",

BIN
images/lowmist.png (Stored with Git LFS)

Binary file not shown.

BIN
images/meditative.gif (Stored with Git LFS)

Binary file not shown.

BIN
images/orb.png (Stored with Git LFS)

Binary file not shown.

BIN
images/owlemons.png (Stored with Git LFS)

Binary file not shown.

BIN
images/passage.png (Stored with Git LFS)

Binary file not shown.

BIN
images/pond.png (Stored with Git LFS)

Binary file not shown.

BIN
images/prairie_ride.png (Stored with Git LFS)

Binary file not shown.

BIN
images/sunflowers.jpg (Stored with Git LFS)

Binary file not shown.

View File

@ -1,23 +1,22 @@
{
"sizes": {
"1x1": [320, 320],
"1x2": [320, 640],
"2x1": [640, 320],
"2x2": [640, 640],
"3x3": [960, 960],
"3x4": [960, 1280],
"4x2": [1280, 640],
"4x3": [1280, 960],
"4x4": [1280, 1280]
"1x1": [640, 640],
"1x2": [640, 1280],
"2x1": [1280, 640],
"2x2": [1280, 1280],
"3x3": [1920, 1920],
"3x4": [1920, 2560],
"4x2": [2560, 1280],
"4x3": [2560, 1920],
"4x4": [2560, 2560]
},
"images": [
{
"name": "graham",
"size": "1x2",
"kz": [1, 4],
"resize": [320, 320],
"frames": [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58],
"_comment": "For some reason this needs to be 1:1"
"name": "burning_skull",
"size": "4x4",
"kz": [8, 12],
"resize": [620, 620],
"frames": [2805, 2826]
}
]
}

BIN
images/tides.png (Stored with Git LFS)

Binary file not shown.

BIN
images/unpacked.jpg (Stored with Git LFS)

Binary file not shown.

BIN
images/water.png (Stored with Git LFS)

Binary file not shown.

BIN
images/wind.png (Stored with Git LFS)

Binary file not shown.

View File

@ -3,23 +3,22 @@
"description": "Replaces the ingame paintings with furry porn.",
"url": "https://git.furry.cool/MCFurryPacks/YiffyPaintings",
"versions": [
["1.06", 1],
["1.09", 2],
["1.11", 3],
["1.13", 4],
["1.14", 4],
["1.15", 5],
["1.16", 6],
["1.17", 7],
["1.18", 8],
["1.19", 9],
["1.06", 1],
["1.09", 2],
["1.11", 3],
["1.13", 4],
["1.14", 4],
["1.15", 5],
["1.16", 6],
["1.17", 7],
["1.18", 8],
["1.19", 9],
["1.19.3", 12],
["1.19.4", 13],
["1.20", 15],
["1.20", 15],
["1.20.2", 18],
["1.20.3", 26],
["1.20.5", 32],
["1.21", 34]
["1.20.5", 32]
],
"exports": [],
"overrides": {

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

Binary file not shown.

View File

@ -1,67 +0,0 @@
1x1
- kebab
- aztec
- alban
- aztec2
- bomb
- plant
- wasteland
- meditative (1.21+)
1x2
- wanderer
- graham
- prairie_ride (1.21+)
2x1
- pool
- courbet
- sunset
- sea
- creebet
2x2
- match
- bust
- stage
- void
- skull_and_roses
- wither
- baroque (1.21+)
- humble (1.21+)
- earth (1.19+)
- wind (1.19+)
- fire (1.19+)
- water (1.19+)
3x3
- bouquet (1.21+)
- cavebird (1.21+)
- cotan (1.21+)
- endboss (1.21+)
- fern (1.21+)
- owlemons (1.21+)
- sunflowers (1.21+)
- tides (1.21+)
3x4
- backyard (1.21+)
- pond (1.21+)
4x2
- fighters
- changing (1.21+)
- finding (1.21+)
- lowmist (1.21+)
- passage (1.21+)
4x3
- skeleton
- donkey_kong
4x4
- pointer
- pigscene
- burning_skull
- unpacked (1.21+)
- orb (1.21+)

View File

@ -8,7 +8,7 @@ import { tmpdir } from "os";
import { basename } from "path";
import sharp, { type OverlayOptions, type Sharp } from "sharp";
export async function formatImage(name: string, image: string | string[], frameColors: number[], framePercent: number, width: number, height: number, resizeWidth?: number, resizeHeight?: number, frames?: { start: number; end?: number; } | number[], saveKz?: boolean) {
export async function formatImage(name: string, image: string | string[], frameColors: number[], framePercent: number, width: number, height: number, resizeWidth?: number, resizeHeight?: number, frames?: [start: number, end: number], saveKz?: boolean) {
saveKz ??= false;
const originalWidth = width, originalHeight = height;
const originalFrameVH = Math.floor(Math.min(originalWidth, originalHeight) * framePercent);
@ -37,18 +37,11 @@ export async function formatImage(name: string, image: string | string[], frameC
let files: string[] = [];
tmpDir = await mkdtemp(`${tmpdir()}/split-frames-`);
if (!Array.isArray(image)) {
let select = "";
if (Array.isArray(frames)) {
select = frames.map(f => `eq(n\\,${f})`).join("+");
} else if (frames) {
select = `gte(n\\, ${frames.start})${frames.end ? `*lte(n\\,${frames.end}` : ""})`;
} else {
select = "n";
}
const [start = 0, end = null] = frames ?? [];
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=${inputWidth}:${inputHeight},select='${select}'`)
.videoFilter(`scale=${inputWidth}:${inputHeight}${end !== null ? `,select='gte(n\\, ${start})*lte(n\\,${end})'` : ""}`)
.addOption("-vsync vfr")
.output(`${tmpDir}/frame%04d.png`)
.on("end", async() => {
@ -63,12 +56,11 @@ export async function formatImage(name: string, image: string | string[], frameC
console.log("Frames extracted to %s, %d total for %s", tmpDir, files.length, name);
resolve();
})
.on("error", async(err) => {
.on("error", async() => {
if (tmpDir) {
await rm(tmpDir, { recursive: true });
}
console.error("Failed to extract frames for %s.", name);
console.error(err);
process.exit(1);
})
.run()
@ -200,62 +192,3 @@ async function createFrame(colors: number[], percent: number, width: number, hei
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();
}

View File

@ -1,9 +1,9 @@
import type { PathLike } from "fs";
import { access } from "fs/promises";
import { dirname, resolve } from "path";
import sharp, { type OverlayOptions } from "sharp";
import sharp from "sharp";
import { parseArgs } from "util";
import { formatImage, makeKZ } from "./common";
import { formatImage } from "./common";
import { readFile } from "fs/promises";
import { writeFile } from "fs/promises";
import { rm } from "fs/promises";
@ -34,6 +34,10 @@ const { values: args } = parseArgs({
short: "k",
default: "data-kz"
},
kzfile: {
type: "string",
default: "kz.png"
},
throw: {
type: "boolean",
short: "t",
@ -48,6 +52,7 @@ const dirExists = async(path: PathLike) => access(path).then(() => true, () => f
const imagesPath = resolve(args.images ?? "images.json");
const imageDir = resolve(args.imagedir ?? dirname(imagesPath));
const outDir = resolve(args.outdir ?? "data");
const kzFile = resolve(args.kzfile ?? "frames/kz.png");
const kzOutDir = resolve(args.kzoutdir ?? "data-kz");
const framePercent = 0.03125;
const frameColors = [0xA47627, 0xA45226, 0x944421, 0xAC581D, 0x8C341C, 0xAC641D, 0xAB6C25, 0xA44424, 0xAC572C, 0xAC4C24, 0xA87824];
@ -57,8 +62,12 @@ if (!await dirExists(imageDir)) {
process.exit(1);
}
if (!await Bun.file(kzFile).exists()) {
console.error("Kz file %s does not exist.", kzFile);
process.exit(1);
}
const ap = (p: string) => resolve(p, "assets/minecraft/textures/painting");
// const ap = (p: string) => resolve(p, "../img");
await rm(`${outDir}/assets`, { recursive: true, force: true });
await rm(`${kzOutDir}/assets`, { recursive: true, force: true });
await mkdir(ap(outDir), { recursive: true });
@ -66,7 +75,7 @@ await mkdir(ap(kzOutDir), { recursive: true });
interface Image {
animation?: { frametime?: number; };
frames?: { start: number; end?: number; } | number[];
frames?: [start: number, end: number];
kz: [x: number, y: number] | null;
name: string;
resize?: [width: number, height: number];
@ -81,8 +90,8 @@ interface Images {
const images = await Bun.file(imagesPath).json() as Images;
const [baseWidth, baseHeight] = images.sizes["1x1"];
let kz = await makeKZ(baseWidth);
const kzCleanup: string[] = [];
let kz = await sharp(kzFile, { limitInputPixels: false }).toBuffer();
for (const image of images.images) {
let img: string | string[];
if (await stat(`${imageDir}/${image.name}`).then(s => s.isDirectory(), () => false)) {