feat: abandon webp

This commit is contained in:
trafficlunar 2026-03-13 22:07:10 +00:00
parent 44be36b501
commit 22fb3a2e30
18 changed files with 34 additions and 39 deletions

View file

@ -15,7 +15,7 @@ const nextConfig: NextConfig = {
pathname: "/tutorial/**",
},
{
pathname: "/guest.webp",
pathname: "/guest.png",
},
],
remotePatterns: [

BIN
public/guest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -49,7 +49,7 @@ export async function PATCH(request: NextRequest) {
if (!image) {
await prisma.user.update({
where: { id: Number(session.user.id) },
data: { image: `/guest.webp`, imageUpdatedAt: new Date() },
data: { image: `/guest.png`, imageUpdatedAt: new Date() },
});
return rateLimit.sendResponse({ success: true });
@ -64,10 +64,10 @@ export async function PATCH(request: NextRequest) {
try {
const buffer = Buffer.from(await image.arrayBuffer());
const webpBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).webp({ quality: 85 }).toBuffer();
const fileLocation = path.join(uploadsDirectory, `${session.user.id}.webp`);
const pngBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).png({ quality: 85 }).toBuffer();
const fileLocation = path.join(uploadsDirectory, `${session.user.id}.png`);
await fs.writeFile(fileLocation, webpBuffer);
await fs.writeFile(fileLocation, pngBuffer);
} catch (error) {
console.error("Error uploading profile picture:", error);
Sentry.captureException(error, { extra: { stage: "upload-profile-picture" } });

View file

@ -126,10 +126,10 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
await Promise.all(
images.map(async (image, index) => {
const buffer = Buffer.from(await image.arrayBuffer());
const webpBuffer = await sharp(buffer).webp({ quality: 85 }).toBuffer();
const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`);
const pngBuffer = await sharp(buffer).png({ quality: 85 }).toBuffer();
const fileLocation = path.join(miiUploadsDirectory, `image${index}.png`);
await fs.writeFile(fileLocation, webpBuffer);
await fs.writeFile(fileLocation, pngBuffer);
}),
);
} catch (error) {

View file

@ -148,10 +148,10 @@ export async function POST(request: NextRequest) {
try {
// Compress and store
const studioWebpBuffer = await sharp(studioBuffer).webp({ quality: 85 }).toBuffer();
const studioFileLocation = path.join(miiUploadsDirectory, "mii.webp");
const studioPngBuffer = await sharp(studioBuffer).png({ quality: 85 }).toBuffer();
const studioFileLocation = path.join(miiUploadsDirectory, "mii.png");
await fs.writeFile(studioFileLocation, studioWebpBuffer);
await fs.writeFile(studioFileLocation, studioPngBuffer);
// Generate a new QR code for aesthetic reasons
const byteString = String.fromCharCode(...qrBytes);
@ -165,10 +165,10 @@ export async function POST(request: NextRequest) {
const codeBuffer = Buffer.from(codeBase64, "base64");
// Compress and store
const codeWebpBuffer = await sharp(codeBuffer).webp({ quality: 85 }).toBuffer();
const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.webp");
const codePngBuffer = await sharp(codeBuffer).png({ quality: 85 }).toBuffer();
const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.png");
await fs.writeFile(codeFileLocation, codeWebpBuffer);
await fs.writeFile(codeFileLocation, codePngBuffer);
await generateMetadataImage(miiRecord, session.user.name!);
} catch (error) {
// Clean up if something went wrong
@ -184,10 +184,10 @@ export async function POST(request: NextRequest) {
await Promise.all(
images.map(async (image, index) => {
const buffer = Buffer.from(await image.arrayBuffer());
const webpBuffer = await sharp(buffer).webp({ quality: 85 }).toBuffer();
const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`);
const pngBuffer = await sharp(buffer).png({ quality: 85 }).toBuffer();
const fileLocation = path.join(miiUploadsDirectory, `image${index}.png`);
await fs.writeFile(fileLocation, webpBuffer);
await fs.writeFile(fileLocation, pngBuffer);
}),
);

View file

@ -32,8 +32,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
if (!searchParamsParsed.success) return rateLimit.sendResponse({ error: searchParamsParsed.error.issues[0].message }, 400);
const { type: imageType } = searchParamsParsed.data;
const fileExtension = imageType === "metadata" ? ".png" : ".webp";
const filePath = path.join(process.cwd(), "uploads", "mii", miiId.toString(), `${imageType}${fileExtension}`);
const filePath = path.join(process.cwd(), "uploads", "mii", miiId.toString(), `${imageType}.png`);
let buffer: Buffer | undefined;
// Only find Mii if image type is 'metadata'
@ -109,7 +108,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
}
return rateLimit.sendResponse(buffer, 200, {
"Content-Type": "image/webp",
"Content-Type": "image/png",
"X-Robots-Tag": "noindex, noimageindex, nofollow",
"Cache-Control": "no-store",
});

View file

@ -47,7 +47,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
type: "profile",
title: `${user.name} (@${user.username}) - TomodachiShare`,
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
images: [user.image ?? "/guest.webp"],
images: [user.image ?? "/guest.png"],
username: user.username,
firstName: user.name,
},
@ -55,7 +55,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
card: "summary",
title: `${user.name} (@${user.username}) - TomodachiShare`,
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
images: [user.image ?? "/guest.webp"],
images: [user.image ?? "/guest.png"],
creator: user.username!,
},
alternates: {

View file

@ -16,7 +16,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
const userId = parsed.data;
const filePath = path.join(process.cwd(), "uploads", "user", `${userId}.webp`);
const filePath = path.join(process.cwd(), "uploads", "user", `${userId}.png`);
try {
const buffer = await fs.readFile(filePath);

View file

@ -63,7 +63,7 @@ export default function Description({ text, className }: Props) {
href={`/profile/${id}`}
className="inline-flex items-center align-bottom gap-1.5 pr-2 bg-orange-100 border border-orange-400 rounded-lg mx-1 text-orange-800 text-xs"
>
<ProfilePicture src={linkedProfile.image || "/guest.webp"} width={24} height={24} className="bg-white rounded-lg border-r border-orange-400" />
<ProfilePicture src={linkedProfile.image || "/guest.png"} width={24} height={24} className="bg-white rounded-lg border-r border-orange-400" />
{linkedProfile.name}
</Link>
);

View file

@ -22,7 +22,7 @@ export default function Dropzone({ onDrop, options, children }: Props) {
onDrop: handleDrop,
maxFiles: 3,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".png", ".heic"],
},
...options,
});

View file

@ -30,7 +30,7 @@ export default async function ProfileInformation({ userId, page }: Props) {
<div className="flex w-full gap-4 overflow-x-scroll">
{/* Profile picture */}
<Link href={`/profile/${user.id}`} className="size-28 aspect-square">
<ProfilePicture src={user.image ?? "/guest.webp"} className="rounded-full bg-white border-2 border-orange-400 shadow max-md:self-center" />
<ProfilePicture src={user.image ?? "/guest.png"} className="rounded-full bg-white border-2 border-orange-400 shadow max-md:self-center" />
</Link>
{/* User information */}
<div className="flex flex-col w-full relative py-3">

View file

@ -9,7 +9,7 @@ export default async function ProfileOverview() {
<li title="Your profile">
<Link href={`/profile/${session?.user.id}`} aria-label="Go to profile" className="pill button gap-2! p-0! h-full max-w-64" data-tooltip="Your Profile">
<Image
src={session?.user?.image ?? "/guest.webp"}
src={session?.user?.image ?? "/guest.png"}
alt="profile picture"
width={40}
height={40}

View file

@ -7,5 +7,5 @@ export default function ProfilePicture(props: Partial<ImageProps>) {
const { src, ...rest } = props;
const [imgSrc, setImgSrc] = useState(src);
return <Image width={128} height={128} {...rest} src={imgSrc || "/guest.webp"} alt={"profile picture"} onError={() => setImgSrc("/guest.webp")} />;
return <Image width={128} height={128} {...rest} src={imgSrc || "/guest.png"} alt={"profile picture"} onError={() => setImgSrc("/guest.png")} />;
}

View file

@ -59,7 +59,7 @@ export default function ProfilePictureSettings() {
</p>
<Image
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.png"}
alt="new profile picture"
width={96}
height={96}
@ -93,7 +93,7 @@ export default function ProfilePictureSettings() {
<div className="bg-orange-100 rounded-xl border-2 border-amber-500 mt-4 px-2 py-1 flex items-center">
<p className="font-semibold mb-2">New profile picture:</p>
<Image
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.png"}
alt="new profile picture"
width={128}
height={128}

View file

@ -43,7 +43,7 @@ export default function ReportUserForm({ user }: Props) {
<hr className="border-zinc-300" />
<div className="bg-orange-100 rounded-xl border-2 border-orange-400 flex p-4 gap-4">
<ProfilePicture src={user.image ?? "/guest.webp"} width={96} height={96} className="aspect-square rounded-full border-2 border-orange-400" />
<ProfilePicture src={user.image ?? "/guest.png"} width={96} height={96} className="aspect-square rounded-full border-2 border-orange-400" />
<div className="flex flex-col justify-center">
<p className="text-xl font-bold overflow-hidden text-ellipsis">{user.name}</p>
<p className="text-sm font-bold overflow-hidden text-ellipsis">@{user.username}</p>

View file

@ -90,7 +90,7 @@ export default function EditForm({ mii, likes }: Props) {
const response = await fetch(path);
const blob = await response.blob();
return Object.assign(new File([blob], `image${index}.webp`, { type: "image/webp" }), { path });
return Object.assign(new File([blob], `image${index}.png`, { type: "image/png" }), { path });
}),
);

View file

@ -130,16 +130,14 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
// Load assets concurrently
const [miiImage, qrCodeImage, fonts] = await Promise.all([
// Read and convert the .webp images to .png (because satori doesn't support it)
fs.readFile(path.join(miiUploadsDirectory, "mii.webp")).then((buffer) =>
// Read and convert the images to data URI
fs.readFile(path.join(miiUploadsDirectory, "mii.png")).then((buffer) =>
sharp(buffer)
.png()
.toBuffer()
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
),
fs.readFile(path.join(miiUploadsDirectory, "qr-code.webp")).then((buffer) =>
fs.readFile(path.join(miiUploadsDirectory, "qr-code.png")).then((buffer) =>
sharp(buffer)
.png()
.toBuffer()
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
),
@ -209,8 +207,6 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
// Store the file
try {
// I tried using .webp here but the quality looked awful
// but it actually might be well-liked due to the hatred of .webp
const fileLocation = path.join(miiUploadsDirectory, "metadata.png");
await fs.writeFile(fileLocation, buffer);
} catch (error) {