Plugin

FixImagesQuality

Improves quality of images by loading them at their original resolution

Media Appearance
index.tsx
Download

Source

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