Plugin
FixImagesQuality
Improves quality of images by loading them at their original resolution
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import { definePluginSettings } from "@api/Settings";8
import { Card } from "@components/Card";9
import { Flex } from "@components/Flex";10
import { Margins } from "@components/margins";11
import { Paragraph } from "@components/Paragraph";12
import { Devs } from "@utils/constants";13
import { Logger } from "@utils/Logger";14
import definePlugin, { OptionType } from "@utils/types";15
16
const 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
24
export 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>— In chat, optimised but full resolution images will be loaded.</li>49
<li>— 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>— Animated images (GIF, WebP, etc.) in chat will always animate, regardless of if the App is focused.</li>56
<li>— 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 images72
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 webp81
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 large87
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