today.isangjeong/app/lib/image.ts

192 lines
6.2 KiB
TypeScript

import path from "path";
import { join } from "path";
import { mkdirSync, existsSync } from "fs";
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
import { getMealInfo, getNutritionInfo, NameToEmoji, removeNutritionInfo } from "./meal"; // 이 함수의 내용은 제공되지 않았으므로 그대로 둡니다.
import { getAllSchedules } from "./schedule";
import { isVTS } from "./vts";
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<{ [key: string]: any } | undefined> {
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 = removeNutritionInfo(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][0] ?? "", 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 (await isVTS(MLSV_YMD)) {
ctx.fillStyle = "#CDAD94";
ctx.fillText("with V.T.S.", 952.5, 245);
} // VTS 관련 코드 주석 처리
const outPath = path.join("./temp", `${MLSV_YMD}.png`); // 경로 수정: "./temp/" -> "./temp"
// temp 폴더가 없으면 생성
if (!existsSync("./temp")) {
mkdirSync("./temp", { recursive: true });
}
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);
} finally {
const meals = removeNutritionInfo(mealInfo.meal).split("\n");
const nutritionInfo = getNutritionInfo(mealInfo.meal);
const retrunValue: { [key: string]: any } = {};
for (let i = 0; i < meals.length; i++) {
const meal = (meals[i] ?? "").trim();
retrunValue[meal] = nutritionInfo[i] || [];
}
return retrunValue;
}
}
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`);
// temp 폴더가 없으면 생성
if (!existsSync("./temp")) {
mkdirSync("./temp", { recursive: true });
}
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);
}
}
}