168 lines
No EOL
5.4 KiB
TypeScript
168 lines
No EOL
5.4 KiB
TypeScript
import path from "path";
|
|
import { join } from "path";
|
|
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
|
|
import { getMealInfo, NameToEmoji } from "./meal"; // 이 함수의 내용은 제공되지 않았으므로 그대로 둡니다.
|
|
import { getAllSchedules } from "./schedule";
|
|
|
|
GlobalFonts.registerFromPath('./template/Pretendard-Bold.ttf', 'Pretendard Bold')
|
|
GlobalFonts.registerFromPath('./template/NotoColorEmoji-Regular.ttf', 'NotoColorEmoji Regular')
|
|
|
|
export class CreateImage {
|
|
static async PostMeal(MLSV_YMD: string): Promise<void> {
|
|
const mealInfo = await getMealInfo(MLSV_YMD);
|
|
const img = await loadImage(path.join("./template/skeleton.png"));
|
|
const canvas = createCanvas(img.width, img.height);
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
ctx.font = "56px Pretendard Bold";
|
|
ctx.fillStyle = "white";
|
|
ctx.textAlign = "left";
|
|
|
|
const lines = mealInfo.meal.split("\n").reverse();
|
|
let emojis = (await NameToEmoji(lines.toString())).split(",");
|
|
|
|
if (lines.length !== emojis.length) {
|
|
console.error("Error: Emojis and lines count mismatch retrying...");
|
|
return this.PostMeal(MLSV_YMD);
|
|
}
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const emoji = emojis[i];
|
|
const text = lines[i];
|
|
|
|
ctx.font = "50px NotoColorEmoji Regular";
|
|
ctx.fillText(emoji ?? "", 75, 930 - i * 60);
|
|
|
|
ctx.font = "56px Pretendard Bold";
|
|
ctx.fillText(text ?? "", 75 + 80, 930 - i * 60);
|
|
}
|
|
|
|
ctx.font = "24px Pretendard Bold";
|
|
ctx.textAlign = "right";
|
|
ctx.fillText(
|
|
`${MLSV_YMD.slice(0, 4)}년 ${MLSV_YMD.slice(4, 6)}월 ${MLSV_YMD.slice(
|
|
6,
|
|
8
|
|
)}일`,
|
|
945,
|
|
110
|
|
);
|
|
ctx.fillStyle = "#89CAFF";
|
|
ctx.fillText(mealInfo.kcal, 945, 220);
|
|
|
|
/*if (vts.VTS임(MLSV_YMD)) {
|
|
ctx.fillStyle = "#CDAD94";
|
|
ctx.fillText("with V.T.S.", 830, 225);
|
|
}*/ // VTS 관련 코드 주석 처리
|
|
|
|
const outPath = path.join("./temp", `${MLSV_YMD}.png`); // 경로 수정: "./temp/" -> "./temp"
|
|
const buffer = canvas.toBuffer("image/png");
|
|
|
|
try {
|
|
await Bun.write(outPath, buffer);
|
|
console.log("🍲 | Meal Info Image Saved");
|
|
} catch (error) {
|
|
console.error("Error saving meal info image:", error);
|
|
}
|
|
}
|
|
|
|
static async PostSchedule() {
|
|
const today = new Date();
|
|
const year = today.getFullYear();
|
|
const month = (today.getMonth() + 1).toString().padStart(2, "0");
|
|
|
|
// 일정 데이터 불러오기
|
|
const allSchedules = await getAllSchedules();
|
|
const events = allSchedules[month] || [];
|
|
|
|
// 이미지 로드
|
|
const image = await loadImage(join("./template/skeleton_schoolevent.png"));
|
|
const canvas = createCanvas(image.width, image.height);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
// 배경 그리기
|
|
ctx.drawImage(image, 0, 0);
|
|
|
|
// 폰트 설정
|
|
const eventFont = "48px Pretendard Bold";
|
|
const detailFont = "24px Pretendard Bold";
|
|
ctx.font = eventFont;
|
|
ctx.fillStyle = "white";
|
|
ctx.textAlign = "left";
|
|
|
|
// 행사 내용 그리기
|
|
let yPosition = 930;
|
|
for (const event of events.slice().reverse()) {
|
|
ctx.fillText(`${event.date} : ${event.desc}`, 75, yPosition);
|
|
yPosition -= 60;
|
|
}
|
|
|
|
// 상단 월 정보
|
|
ctx.font = detailFont;
|
|
ctx.textAlign = "right";
|
|
ctx.fillText(`${year}년 ${parseInt(month)}월`, 945, 110);
|
|
|
|
// 파일 저장
|
|
const outputFilePath = join("./", `temp/schedule-${year}-${month}.png`);
|
|
const buffer = canvas.toBuffer("image/png");
|
|
require("fs").writeFileSync(outputFilePath, buffer);
|
|
console.log("📅 | School Event Image Saved");
|
|
|
|
return outputFilePath;
|
|
}
|
|
|
|
static async ConvertToStory(filePath: string) {
|
|
const inPath = path.join("./",`${filePath}`);
|
|
let img;
|
|
try {
|
|
img = await loadImage(inPath);
|
|
} catch (error) {
|
|
console.error(`Error loading image for story: ${inPath}`, error);
|
|
// Post 메서드가 먼저 호출되어 이미지가 생성되었는지 확인 필요
|
|
// 또는 에러 처리를 통해 사용자에게 알림
|
|
|
|
return;
|
|
}
|
|
|
|
const width = img.width;
|
|
const height = img.height;
|
|
const aspectRatio = 9 / 16;
|
|
const currentRatio = width / height;
|
|
|
|
let newWidth = width;
|
|
let newHeight = height;
|
|
|
|
if (currentRatio < aspectRatio) {
|
|
newWidth = Math.floor(height * aspectRatio);
|
|
} else {
|
|
newHeight = Math.floor(width / aspectRatio);
|
|
}
|
|
|
|
const canvas = createCanvas(newWidth, newHeight);
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.fillStyle = "#0A0A0A"; // 배경색
|
|
ctx.fillRect(0, 0, newWidth, newHeight);
|
|
|
|
// 원본 이미지를 새 캔버스 중앙에 맞추어 약간 작게(85%) 그립니다.
|
|
const resizedWidth = Math.floor(width * 0.85);
|
|
const resizedHeight = Math.floor(height * 0.85);
|
|
|
|
const offsetX = Math.floor((newWidth - resizedWidth) / 2);
|
|
const offsetY = Math.floor((newHeight - resizedHeight) / 2);
|
|
|
|
ctx.drawImage(img, offsetX, offsetY, resizedWidth, resizedHeight);
|
|
|
|
const extIndex = filePath.lastIndexOf(".");
|
|
const baseName = extIndex !== -1 ? filePath.substring(0, extIndex) : filePath;
|
|
const outPath = path.join("./", `${baseName}-story.png`);
|
|
const buffer = canvas.toBuffer("image/png");
|
|
|
|
try {
|
|
await Bun.write(outPath, buffer);
|
|
console.log("🔄️ | Story Convert Success");
|
|
} catch (error) {
|
|
console.error("Error saving story info image:", error);
|
|
}
|
|
}
|
|
} |