dominionator/src/draw.ts

288 lines
6.4 KiB
TypeScript
Raw Normal View History

2025-01-06 23:01:01 -05:00
import {
measureDominionText,
parse,
renderDominionText,
} from "./dominiontext.ts";
2025-01-06 23:34:41 -05:00
import { DominionCardType, TYPE_ACTION } from "./types.ts";
2024-12-29 23:00:38 -05:00
import { DominionCard } from "./types.ts";
2023-12-27 11:37:37 -08:00
2024-12-31 11:53:45 -05:00
const imageCache: Record<string, HTMLImageElement> = {};
export const loadImage = (
2025-01-06 23:34:41 -05:00
src: string,
key?: string
): Promise<HTMLImageElement | null> => {
2024-12-31 11:53:45 -05:00
return new Promise((resolve) => {
2025-01-06 23:34:41 -05:00
if (key && key in imageCache && imageCache[key]) {
2024-12-31 11:53:45 -05:00
resolve(imageCache[key]);
}
const img = new Image();
img.onload = () => {
2025-01-06 23:34:41 -05:00
if (key) {
imageCache[key] = img;
}
2024-12-31 11:53:45 -05:00
resolve(img);
};
2025-01-04 22:32:17 -05:00
img.onerror = (e) => {
console.log("err", e);
2025-01-06 23:34:41 -05:00
resolve(null);
2025-01-04 22:32:17 -05:00
};
2024-12-31 11:53:45 -05:00
img.src = src;
});
};
const imageList = [
{
2025-01-04 22:32:17 -05:00
key: "card-color-1",
src: "/static/assets/CardColorOne.png",
},
2025-01-07 20:18:42 -08:00
{
key: "card-color-2",
src: "/static/assets/CardColorTwo.png",
},
2025-01-04 22:32:17 -05:00
{
key: "card-brown",
src: "/static/assets/CardBrown.png",
},
{
key: "card-gray",
src: "/static/assets/CardGray.png",
},
{
key: "card-description-focus",
src: "/static/assets/DescriptionFocus.png",
2024-12-31 11:53:45 -05:00
},
2025-01-06 21:12:28 -05:00
{
key: "coin",
src: "/static/assets/Coin.png",
},
2025-01-06 21:06:13 -08:00
{
key: "debt",
src: "/static/assets/Debt.png",
},
{
key: "potion",
src: "/static/assets/Potion.png",
},
2025-01-07 08:10:47 -08:00
{
key: "vp",
src: "/static/assets/VP.png",
},
{
key: "vp-token",
src: "/static/assets/VP-Token.png",
},
2025-01-07 22:17:05 -08:00
{
key: "sun",
src: "/static/assets/Sun.png",
},
2024-12-31 11:53:45 -05:00
];
2025-01-04 22:32:17 -05:00
export const loadImages = async () => {
for (const imageInfo of imageList) {
const { key, src } = imageInfo;
2025-01-06 23:34:41 -05:00
await loadImage(src, key);
2025-01-04 22:32:17 -05:00
}
};
2024-12-31 11:53:45 -05:00
export const getImage = (key: string) => {
const image = imageCache[key];
if (!image) {
throw Error(`Tried to get an invalid image ${key}`);
}
return image;
};
2025-01-04 22:32:17 -05:00
export const colorImage = (
image: HTMLImageElement,
color?: string
2025-01-04 22:32:17 -05:00
): HTMLCanvasElement => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext("2d")!;
context.save();
context.drawImage(image, 0, 0);
context.globalCompositeOperation = "multiply";
context.fillStyle = color ?? "white";
2025-01-04 22:32:17 -05:00
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = "destination-atop"; // restore transparency
context.drawImage(image, 0, 0);
context.restore();
return canvas;
};
2024-12-29 23:00:38 -05:00
export const drawCard = (
context: CanvasRenderingContext2D,
card: DominionCard
): Promise<void> => {
2023-12-27 11:37:37 -08:00
if (card.orientation === "card") {
2024-12-29 23:00:38 -05:00
return drawStandardCard(context, card);
2023-12-27 11:37:37 -08:00
} else {
2024-12-29 23:00:38 -05:00
return drawLandscapeCard(context, card);
2023-12-27 11:37:37 -08:00
}
2024-12-29 23:00:38 -05:00
};
2023-12-27 11:37:37 -08:00
2025-01-07 20:18:42 -08:00
const getColors = (
types: DominionCardType[]
): { primary: string; secondary: string | null } => {
2025-01-06 23:01:01 -05:00
const byPriority = [...types]
.filter((type) => type.color)
.sort((a, b) => b.color!.priority - a.color!.priority);
2025-01-07 20:18:42 -08:00
const priority1 = byPriority[0]!;
let primary = priority1.color?.value ?? "white";
let secondary = byPriority[1]?.color?.value ?? null;
if (priority1 === TYPE_ACTION) {
2025-01-06 23:01:01 -05:00
const overriders = byPriority.filter((t) => t.color!.overridesAction);
if (overriders.length) {
2025-01-07 20:18:42 -08:00
primary = overriders[0]!.color!.value;
}
if (primary === secondary) {
secondary = byPriority[2]?.color?.value ?? null;
2025-01-06 23:01:01 -05:00
}
}
2025-01-07 20:18:42 -08:00
return { primary, secondary };
2025-01-06 23:01:01 -05:00
};
2024-12-29 23:00:38 -05:00
const drawStandardCard = async (
context: CanvasRenderingContext2D,
2025-01-06 22:28:57 -05:00
card: DominionCard & { orientation: "card" }
2024-12-29 23:00:38 -05:00
): Promise<void> => {
const w = context.canvas.width;
const h = context.canvas.height;
2025-01-06 21:06:13 -08:00
let size;
2024-12-29 23:00:38 -05:00
context.save();
2025-01-04 22:32:17 -05:00
// Draw the image
2025-01-06 23:34:41 -05:00
const image = await loadImage(card.image);
if (image) {
const cx = w / 2;
const cy = 704;
const windowHeight = 830;
const windowWidth = 1194;
const scale = Math.max(
windowHeight / image.height,
windowWidth / image.width
);
context.drawImage(
image,
cx - (scale * image.width) / 2,
cy - (scale * image.height) / 2,
scale * image.width,
scale * image.height
);
}
2025-01-04 22:32:17 -05:00
// Draw the card base
2025-01-07 20:18:42 -08:00
const colors = getColors(card.types); // "#ffbc55";
if (colors.secondary) {
context.drawImage(
colorImage(getImage("card-color-1"), colors.secondary),
0,
0
);
context.drawImage(
colorImage(getImage("card-color-2"), colors.primary),
0,
0
);
} else {
context.drawImage(
colorImage(getImage("card-color-1"), colors.primary),
0,
0
);
context.drawImage(getImage("card-description-focus"), 44, 1094);
}
2025-01-04 22:32:17 -05:00
context.drawImage(getImage("card-gray"), 0, 0);
context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0);
// Draw the name
2025-01-06 21:06:13 -08:00
size = 78;
context.font = `${size}pt DominionTitle`;
while (
(await measureDominionText(context, parse(card.title))).width > 1050
) {
size -= 1;
context.font = `${size}pt DominionTitle`;
}
await renderDominionText(context, parse(card.title), w / 2, 220);
2025-01-04 22:32:17 -05:00
// Draw the description
2025-01-06 21:12:28 -05:00
context.font = "60pt DominionText";
await renderDominionText(
2025-01-05 23:55:22 -05:00
context,
2025-01-07 20:02:50 -08:00
parse(card.description, { isDescription: true }),
2025-01-05 23:55:22 -05:00
w / 2,
2025-01-06 23:51:51 -05:00
1490,
1000
2025-01-05 23:55:22 -05:00
);
2025-01-07 20:02:50 -08:00
console.log(card.title, parse(card.description));
2025-01-04 22:32:17 -05:00
// Draw the types
2025-01-06 21:06:13 -08:00
size = 65;
2025-01-06 23:01:01 -05:00
context.font = `${size}pt DominionTitle`;
while (
(
await measureDominionText(
context,
parse(card.types.map((t) => t.name).join(" - "))
)
).width > 800
) {
size -= 1;
context.font = `${size}pt DominionTitle`;
}
await renderDominionText(
context,
parse(card.types.map((t) => t.name).join(" - ")),
w / 2,
1930,
800
);
2025-01-04 22:32:17 -05:00
// Draw the cost
2025-01-06 22:13:53 -05:00
context.font = "90pt DominionText";
2025-01-06 21:06:13 -08:00
const costMeasure = await measureDominionText(context, parse(card.cost));
await renderDominionText(
context,
parse(card.cost),
130 + costMeasure.width / 2,
1940
);
2025-01-04 22:32:17 -05:00
// Draw the preview
2025-01-06 22:28:57 -05:00
if (card.preview) {
context.font = "90pt DominionText";
2025-01-06 21:06:13 -08:00
await renderDominionText(context, parse(card.preview), 200, 210);
await renderDominionText(context, parse(card.preview), w - 200, 210);
2025-01-06 22:28:57 -05:00
}
2025-01-04 22:32:17 -05:00
// Draw the icon
2025-01-06 23:34:41 -05:00
// Draw the author credit
context.fillStyle = "white";
context.font = "31pt DominionText";
const authorMeasure = await measureDominionText(
context,
parse(card.author)
);
await renderDominionText(
context,
parse(card.author),
w - 150 - authorMeasure.width / 2,
2025-01-06 21:06:13 -08:00
2035
2025-01-06 23:34:41 -05:00
);
// Draw the artist credit
const artistMeasure = await measureDominionText(
context,
parse(card.artist)
);
await renderDominionText(
context,
parse(card.artist),
155 + artistMeasure.width / 2,
2025-01-06 21:06:13 -08:00
2035
2025-01-06 23:34:41 -05:00
);
// Restore the context
2024-12-29 23:00:38 -05:00
context.restore();
};
2023-12-27 11:37:37 -08:00
2024-12-29 23:00:38 -05:00
const drawLandscapeCard = async (
context: CanvasRenderingContext2D,
2025-01-06 22:28:57 -05:00
card: DominionCard & { orientation: "landscape" }
2024-12-29 23:00:38 -05:00
): Promise<void> => {
2023-12-27 11:37:37 -08:00
// TODO: everything
2024-12-29 23:00:38 -05:00
};