From 85380f30a2e56b18fcc55d7e3c91b3a815b459be Mon Sep 17 00:00:00 2001 From: Kiradien <68683574+Kiradien@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:36:06 -0400 Subject: [PATCH] #2870 Enhanced animation detection Overall enhancement to the detection of animated images. Code only runs when users wish to maintain animations, otherwise the function call immediately returns. --- plugin/_locales/en/messages.json | 4 ++++ plugin/js/ImageCollector.js | 35 +++++++++++++++++++++++++++++++- plugin/js/UserPreferences.js | 1 + plugin/popup.html | 8 +++++++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/plugin/_locales/en/messages.json b/plugin/_locales/en/messages.json index 716dcfa3..0a294fd3 100644 --- a/plugin/_locales/en/messages.json +++ b/plugin/_locales/en/messages.json @@ -267,6 +267,10 @@ "message": "Compress Images", "description": "Label for checkbox and value to compress images to a certain size" }, + "__MSG_label_Compress_Images_Animated__": { + "message": "Compress Animated Images to single frame (GIF, APNG, WebP)", + "description": "Label for animated compression checkbox" + }, "__MSG_label_Compress_Images_JPG_Cover__": { "message": "Restrict Cover Image to JPEG", "description": "Label for JPEG cover restriction checkbox" diff --git a/plugin/js/ImageCollector.js b/plugin/js/ImageCollector.js index c5e76d46..3cfb225a 100644 --- a/plugin/js/ImageCollector.js +++ b/plugin/js/ImageCollector.js @@ -320,9 +320,42 @@ class ImageCollector { }); } + isAnimatedImage(imageInfo) { + const animatedTypes = ["image/gif", "image/png", "image/webp"]; + + if (animatedTypes.includes(imageInfo.mediaType)) + { + const maxScanBytes = 65536; + const buffer = imageInfo.arraybuffer; + const scanLimit = Math.min(buffer.byteLength, maxScanBytes); + const view = new DataView(buffer, 0, scanLimit); + const limit = Math.max(0, view.byteLength - 4); + + try { + for (let i = 0; i < limit; i++) { + const current32 = view.getUint32(i, false); + + if ((imageInfo.mediaType == "image/webp" && (current32 === 0x414E494D || current32 === 0x414E4D46)) + || (imageInfo.mediaType == "image/png" && current32 === 0x6163544C) + || (imageInfo.mediaType == "image/gif" && (current32 >>> 8) === 0x21F904) + ) return true; + } + } + catch (e) { + console.error("Binary animation detection exception:", e); + } + } + return false; + } + + skipAnimated(imageInfo) { + if (this.userPreferences.compressImagesAnimated.value) return false; + return this.isAnimatedImage(imageInfo); + } + runCompression(imageInfo, img) { return new Promise((resolve, reject) => { - if (this.userPreferences.compressImages.value && imageInfo.mediaType != "image/gif") + if (this.userPreferences.compressImages.value && !this.skipAnimated(imageInfo)) { let outputType = "image/jpeg"; switch (this.userPreferences.compressImagesType.value) { diff --git a/plugin/js/UserPreferences.js b/plugin/js/UserPreferences.js index 45c4674a..c5591e02 100644 --- a/plugin/js/UserPreferences.js +++ b/plugin/js/UserPreferences.js @@ -112,6 +112,7 @@ class UserPreferences { // eslint-disable-line no-unused-vars this.overrideMinimumDelay = this.addPreference("overrideMinimumDelay", "overrideMinimumDelayCheckbox", false); this.skipImages = this.addPreference("skipImages", "skipImagesCheckbox", false); this.compressImages = this.addPreference("compressImages", "compressImagesCheckbox", false); + this.compressImagesAnimated = this.addPreference("compressImagesAnimated", "compressImagesAnimatedCheckbox", true); this.compressImagesJpgCover = this.addPreference("compressImagesJpgCover", "compressImagesJpgCoverCheckbox", false); this.compressImagesType = this.addPreference("compressImagesType", "compressImagesType", "jpg"); this.compressImagesMaxResolution = this.addPreference("compressImagesMaxResolution", "compressImagesMaxResolutionTag", "1080"); diff --git a/plugin/popup.html b/plugin/popup.html index 44740ef4..6a2fb9db 100644 --- a/plugin/popup.html +++ b/plugin/popup.html @@ -1,4 +1,4 @@ - + @@ -387,6 +387,12 @@

Instructions

__MSG_label_Compress_Images_JPG_Cover__ + + + + + __MSG_label_Compress_Images_Animated__ +