Plugin

FixImagesQuality

Improves quality of images by loading them at their original resolution

Media Appearance
index.tsx
Download

Source

src/plugins/fixImagesQuality/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { definePluginSettings } from "@api/Settings";
8import { Card } from "@components/Card";
9import { Flex } from "@components/Flex";
10import { Margins } from "@components/margins";
11import { Paragraph } from "@components/Paragraph";
12import { Devs } from "@utils/constants";
13import { Logger } from "@utils/Logger";
14import definePlugin, { OptionType } from "@utils/types";
15
16const settings = definePluginSettings({
17 originalImagesInChat: {
18 type: OptionType.BOOLEAN,
19 description: "Also load the original image in Chat. WARNING: Read the caveats above",
20 default: false,
21 }
22});
23
24export default definePlugin({
25 name: "FixImagesQuality",
26 description: "Improves quality of images by loading them at their original resolution",
27 tags: ["Media", "Appearance"],
28 authors: [Devs.Nuckyz, Devs.Ven],
29 settings,
30
31 patches: [
32 {
33 find: ".handleImageLoad)",
34 replacement: {
35 match: /getSrc\(\i\)\{/,
36 replace: "$&var _vcSrc=$self.getSrc(this?.props,arguments[1]);if(_vcSrc)return _vcSrc;"
37 }
38 }
39 ],
40
41 settingsAboutComponent() {
42 return (
43 <Card variant="normal">
44 <Flex flexDirection="column" gap="4px">
45 <Paragraph size="md" weight="semibold">The default behaviour is the following:</Paragraph>
46 <Paragraph>
47 <ul>
48 <li>&mdash; In chat, optimised but full resolution images will be loaded.</li>
49 <li>&mdash; In the image modal, the original image will be loaded.</li>
50 </ul>
51 </Paragraph>
52 <Paragraph size="md" weight="semibold" className={Margins.top8}>You can also enable original image in chat, but beware of the following caveats:</Paragraph>
53 <Paragraph>
54 <ul>
55 <li>&mdash; Animated images (GIF, WebP, etc.) in chat will always animate, regardless of if the App is focused.</li>
56 <li>&mdash; May cause lag.</li>
57 </ul>
58 </Paragraph>
59 </Flex>
60 </Card>
61 );
62 },
63
64 getSrc(props: { src: string; width: number; height: number; contentType: string; mosaicStyleAlt?: boolean; trigger?: string; }, freeze?: boolean) {
65 if (!props?.src) return;
66
67 try {
68 const { contentType, height, src, width, mosaicStyleAlt, trigger } = props;
69
70 // Embed images do not have a content type set.
71 // It's difficult to differentiate between images and videos. but mosaicStyleAlt seems exclusive to images
72 const isImage = contentType?.startsWith("image/") ?? (typeof mosaicStyleAlt === "boolean");
73 if (!isImage || src.startsWith("data:")) return;
74
75 const url = new URL(src);
76 if (!url.pathname.startsWith("/attachments/")) return;
77
78 url.searchParams.set("animated", String(!freeze));
79 if (freeze && url.pathname.endsWith(".gif")) {
80 // gifs don't support animated=false, so we have no choice but to use webp
81 url.searchParams.set("format", "webp");
82 }
83
84 const isModal = !!trigger;
85 if (!settings.store.originalImagesInChat && !isModal) {
86 // make sure the image is not too large
87 const pixels = width * height;
88 const limit = 2000 * 1200;
89
90 if (pixels <= limit)
91 return url.toString();
92
93 const scale = Math.sqrt(pixels / limit);
94
95 url.searchParams.set("width", Math.round(width / scale).toString());
96 url.searchParams.set("height", Math.round(height / scale).toString());
97 return url.toString();
98 }
99
100 url.hostname = "cdn.discordapp.com";
101 return url.toString();
102 } catch (e) {
103 new Logger("FixImagesQuality").error("Failed to make image src", e);
104 return;
105 }
106 }
107});
108