From 4e03901225fef873217913c3573cc85766e0ee67 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Wed, 4 Dec 2024 17:19:02 +0800 Subject: [PATCH 01/18] 1 1 --- .../cache_managers/image_cache_manager.dart | 28 ++++++++++++++++++- .../lib/src/compat/file_service_compat.dart | 16 ++++++----- flutter_cache_manager/pubspec.yaml | 2 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 614d7d9d..d0c1383a 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -61,6 +61,31 @@ mixin ImageCacheManager on BaseCacheManager { _runningResizes.remove(resizedKey); } + Future getExt(File file) async { + if (!await file.exists()) { + debugPrint('文件不存在: ${file.path}'); + return false; + } + final raf = await file.open(); + final header = await raf.read(8); + await raf.close(); + final hexString = + header.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); + if (hexString.startsWith('ffd8')) { + return true; // JPEG + } else if (hexString.startsWith('89504e47')) { + return true; // PNG + } else if (hexString.startsWith('00000200')) { + return true; // TGA + } else if (hexString.startsWith('00000100')) { + return true; // CUR + } else if (hexString.startsWith('00000000')) { + return true; // ICO + } + + return false; // 不支持的文件类型 + } + final Map> _runningResizes = {}; Future _resizeImageFile( @@ -69,9 +94,10 @@ mixin ImageCacheManager on BaseCacheManager { int? maxWidth, int? maxHeight, ) async { + bool isSupported = await getExt(originalFile.file); final originalFileName = originalFile.file.path; final fileExtension = originalFileName.split('.').last; - if (!supportedFileNames.contains(fileExtension)) { + if (isSupported) { return originalFile; } diff --git a/flutter_cache_manager/lib/src/compat/file_service_compat.dart b/flutter_cache_manager/lib/src/compat/file_service_compat.dart index 0df201e1..bbd7a6ff 100644 --- a/flutter_cache_manager/lib/src/compat/file_service_compat.dart +++ b/flutter_cache_manager/lib/src/compat/file_service_compat.dart @@ -63,13 +63,15 @@ class CompatFileServiceGetResponse implements FileServiceResponse { @override String get fileExtension { - var fileExtension = ''; - final contentTypeHeader = _header(HttpHeaders.contentTypeHeader); - if (contentTypeHeader != null) { - final contentType = ContentType.parse(contentTypeHeader); - fileExtension = contentType.fileExtension; - } - return fileExtension; + // var fileExtension = ''; + // final contentTypeHeader = _header(HttpHeaders.contentTypeHeader); + // if (contentTypeHeader != null) { + // final contentType = ContentType.parse(contentTypeHeader); + // fileExtension = contentType.fileExtension; + // } + // return fileExtension; + + return '.jpeg'; } @override diff --git a/flutter_cache_manager/pubspec.yaml b/flutter_cache_manager/pubspec.yaml index 4e49f688..ed849972 100644 --- a/flutter_cache_manager/pubspec.yaml +++ b/flutter_cache_manager/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: uuid: ^4.4.2 dev_dependencies: - build_runner: ^2.4.12 + build_runner: ^2.4.9 flutter_lints: ^4.0.0 flutter_test: sdk: flutter From 74e71ca96a38432cf4b15fb870e2cb1d98fd4e2a Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Wed, 4 Dec 2024 17:37:42 +0800 Subject: [PATCH 02/18] Update file_service_compat.dart --- .../lib/src/compat/file_service_compat.dart | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/flutter_cache_manager/lib/src/compat/file_service_compat.dart b/flutter_cache_manager/lib/src/compat/file_service_compat.dart index bbd7a6ff..0df201e1 100644 --- a/flutter_cache_manager/lib/src/compat/file_service_compat.dart +++ b/flutter_cache_manager/lib/src/compat/file_service_compat.dart @@ -63,15 +63,13 @@ class CompatFileServiceGetResponse implements FileServiceResponse { @override String get fileExtension { - // var fileExtension = ''; - // final contentTypeHeader = _header(HttpHeaders.contentTypeHeader); - // if (contentTypeHeader != null) { - // final contentType = ContentType.parse(contentTypeHeader); - // fileExtension = contentType.fileExtension; - // } - // return fileExtension; - - return '.jpeg'; + var fileExtension = ''; + final contentTypeHeader = _header(HttpHeaders.contentTypeHeader); + if (contentTypeHeader != null) { + final contentType = ContentType.parse(contentTypeHeader); + fileExtension = contentType.fileExtension; + } + return fileExtension; } @override From b6105f7e80d1b60253acae2106a996dfa8c6485b Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Wed, 4 Dec 2024 17:39:30 +0800 Subject: [PATCH 03/18] Update image_cache_manager.dart --- .../lib/src/cache_managers/image_cache_manager.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index d0c1383a..7de21ac4 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -63,7 +63,7 @@ mixin ImageCacheManager on BaseCacheManager { Future getExt(File file) async { if (!await file.exists()) { - debugPrint('文件不存在: ${file.path}'); + debugPrint('file is not found: ${file.path}'); return false; } final raf = await file.open(); @@ -83,7 +83,7 @@ mixin ImageCacheManager on BaseCacheManager { return true; // ICO } - return false; // 不支持的文件类型 + return false; } final Map> _runningResizes = {}; From 60286186967dcca9668a68b1dd19100a76506822 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Wed, 4 Dec 2024 17:57:16 +0800 Subject: [PATCH 04/18] fix: fix method name --- .../lib/src/cache_managers/image_cache_manager.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 7de21ac4..57ba59d8 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -61,7 +61,7 @@ mixin ImageCacheManager on BaseCacheManager { _runningResizes.remove(resizedKey); } - Future getExt(File file) async { + Future _isImage(File file) async { if (!await file.exists()) { debugPrint('file is not found: ${file.path}'); return false; @@ -94,10 +94,10 @@ mixin ImageCacheManager on BaseCacheManager { int? maxWidth, int? maxHeight, ) async { - bool isSupported = await getExt(originalFile.file); + bool isImage = await _isImage(originalFile.file); final originalFileName = originalFile.file.path; final fileExtension = originalFileName.split('.').last; - if (isSupported) { + if (!isImage) { return originalFile; } From 4ec2651d1cc9cd728fa7e9880bb10757aa59c5c0 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 5 Dec 2024 11:36:55 +0800 Subject: [PATCH 05/18] Update build.gradle --- flutter_cache_manager/example/android/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index d2ffbffa..047207a5 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -16,3 +16,5 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir } + +ext.kotlin_version = '1.7.10' \ No newline at end of file From b8ebe93a17b3d4212d7e58ca5326ca1100b092c1 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 5 Dec 2024 14:20:52 +0800 Subject: [PATCH 06/18] Update build.gradle --- flutter_cache_manager/example/android/build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index 047207a5..c5e4d410 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -1,3 +1,7 @@ +buildscript { + ext.kotlin_version = '1.7.10' +} + allprojects { repositories { google() @@ -17,4 +21,3 @@ tasks.register("clean", Delete) { delete rootProject.buildDir } -ext.kotlin_version = '1.7.10' \ No newline at end of file From 1d73d2eb92848b0587ada402673812d3a47cb67f Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 5 Dec 2024 14:26:13 +0800 Subject: [PATCH 07/18] 1 --- flutter_cache_manager/example/android/build.gradle | 2 +- flutter_cache_manager/example/android/settings.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index c5e4d410..2cda7602 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.22' } allprojects { diff --git a/flutter_cache_manager/example/android/settings.gradle b/flutter_cache_manager/example/android/settings.gradle index 536165d3..8a53adcd 100644 --- a/flutter_cache_manager/example/android/settings.gradle +++ b/flutter_cache_manager/example/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false } include ":app" From 64ff206ec8ee542b0c7626fa65fac21c89f94066 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 5 Dec 2024 14:34:18 +0800 Subject: [PATCH 08/18] Update build.gradle --- flutter_cache_manager/example/android/build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index 2cda7602..457de897 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -1,7 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.22' -} - allprojects { repositories { google() From 1a55b01c2fcf46a0bb9dc5e47d38727ae7b34b5e Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 5 Dec 2024 14:36:23 +0800 Subject: [PATCH 09/18] Update build.gradle --- flutter_cache_manager/example/android/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index 457de897..8e9de9d8 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -15,5 +15,4 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir -} - +} \ No newline at end of file From 2132fb42c07144b64684f338fc3b5c2406a0c977 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 09:42:27 +0800 Subject: [PATCH 10/18] fix(image): detect resizable images by content instead of url extension The resizer decided whether a file could be resized from the extension of the file on disk. That extension is derived from the url, so images served from extension-less urls, or from urls whose extension does not match the payload, were never resized. It also treated any zero-prefixed binary as an ico. - Detect jpeg, png, ico, cur and webp by their magic number, reading 12 bytes instead of 8 (webp needs the RIFF/WEBP marker at offset 8). - Keep an extension fallback for formats without a signature at the start of the file, like tga, which only stores a signature in its footer. - Read the header in a helper that always closes the file handle. - Restore the trailing newline in example/android/build.gradle, which was dropped by an earlier commit in this branch. - Bump to 3.4.6 with a dated changelog entry, as CONTRIBUTING now requires. Tests: cover an image served without an extension and a non-image payload served with an image extension. --- flutter_cache_manager/CHANGELOG.md | 4 + .../example/android/build.gradle | 2 +- .../cache_managers/image_cache_manager.dart | 78 ++++++++++++++----- flutter_cache_manager/pubspec.yaml | 2 +- .../test/image_cache_manager_test.dart | 37 +++++++++ 5 files changed, 100 insertions(+), 23 deletions(-) diff --git a/flutter_cache_manager/CHANGELOG.md b/flutter_cache_manager/CHANGELOG.md index 9513fb80..8ac9dfb4 100644 --- a/flutter_cache_manager/CHANGELOG.md +++ b/flutter_cache_manager/CHANGELOG.md @@ -1,3 +1,7 @@ +## [3.4.6] - 2026-10-19 + +* Detects whether a downloaded file can be resized from its content instead of from the extension of its url, so images served from extension-less urls or with a non-image `Content-Type` are resized, and responses that are no longer images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) + ## [3.4.5] - 2026-09-18 * Prevents image resize decoding from completing more than once and propagates decoding errors ([#400](https://github.com/Baseflow/flutter_cache_manager/issues/400)). diff --git a/flutter_cache_manager/example/android/build.gradle b/flutter_cache_manager/example/android/build.gradle index 8e9de9d8..d2ffbffa 100644 --- a/flutter_cache_manager/example/android/build.gradle +++ b/flutter_cache_manager/example/android/build.gradle @@ -15,4 +15,4 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir -} \ No newline at end of file +} diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 6fb17f7d..52aae85a 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -7,6 +7,21 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; + +/// Signatures (magic numbers) of the image formats that can be recognized by +/// the first bytes of the file, instead of by its extension. +const _imageSignatures = >[ + [0xFF, 0xD8, 0xFF], // jpeg + [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png + [0x00, 0x00, 0x01, 0x00], // ico + [0x00, 0x00, 0x02, 0x00], // cur +]; + +/// Extensions of the formats that have no signature at the start of the file, +/// so the only way to recognize them is by their extension. Tga for example +/// only stores a signature in its footer. +const _extensionOnlyFormats = ['tga']; + mixin ImageCacheManager on BaseCacheManager { /// Returns a resized image file to fit within maxHeight and maxWidth. It /// tries to keep the aspect ratio. It stores the resized image by adding @@ -65,30 +80,28 @@ mixin ImageCacheManager on BaseCacheManager { _runningResizes.remove(resizedKey); } + /// Whether [file] holds an image that can be decoded and resized, based on + /// the content of the file rather than on the extension of its url. A + /// response can be served without a file extension, or with an extension + /// that does not match the actual content, for example when it is served + /// with an incorrect `Content-Type` header. Future _isImage(File file) async { if (!await file.exists()) { - debugPrint('file is not found: ${file.path}'); return false; } - final raf = await file.open(); - final header = await raf.read(8); - await raf.close(); - final hexString = header - .map((byte) => byte.toRadixString(16).padLeft(2, '0')) - .join(''); - if (hexString.startsWith('ffd8')) { - return true; // JPEG - } else if (hexString.startsWith('89504e47')) { - return true; // PNG - } else if (hexString.startsWith('00000200')) { - return true; // TGA - } else if (hexString.startsWith('00000100')) { - return true; // CUR - } else if (hexString.startsWith('00000000')) { - return true; // ICO - } - return false; + final header = await _readHeader(file); + if (_imageSignatures.any((signature) => _startsWith(header, signature))) { + return true; + } + // WebP is a RIFF container: "RIFF" + 4 bytes of length + "WEBP". + if (header.length >= 12 && + _startsWith(header, 'RIFF'.codeUnits) && + _startsWith(header.sublist(8), 'WEBP'.codeUnits)) { + return true; + } + final extension = file.path.split('.').last.toLowerCase(); + return _extensionOnlyFormats.contains(extension); } final Map> _runningResizes = {}; @@ -99,10 +112,9 @@ mixin ImageCacheManager on BaseCacheManager { int? maxWidth, int? maxHeight, ) async { - bool isImage = await _isImage(originalFile.file); final originalFileName = originalFile.file.path; final fileExtension = originalFileName.split('.').last; - if (!isImage) { + if (!await _isImage(originalFile.file)) { return originalFile; } @@ -174,6 +186,30 @@ mixin ImageCacheManager on BaseCacheManager { } } +/// Reads the first bytes of [file], up to [length] bytes, which is enough to +/// hold the longest signature that is checked. +Future> _readHeader(File file, {int length = 12}) async { + final raf = await file.open(); + try { + return await raf.read(length); + } finally { + await raf.close(); + } +} + +/// Whether [bytes] starts with [signature]. +bool _startsWith(List bytes, List signature) { + if (bytes.length < signature.length) { + return false; + } + for (var i = 0; i < signature.length; i++) { + if (bytes[i] != signature[i]) { + return false; + } + } + return true; +} + Future _decodeImage( File file, { int? width, diff --git a/flutter_cache_manager/pubspec.yaml b/flutter_cache_manager/pubspec.yaml index 3f0c9ce3..8456c4b5 100644 --- a/flutter_cache_manager/pubspec.yaml +++ b/flutter_cache_manager/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_cache_manager description: Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite. -version: 3.4.5 +version: 3.4.6 homepage: https://github.com/Baseflow/flutter_cache_manager topics: - cache diff --git a/flutter_cache_manager/test/image_cache_manager_test.dart b/flutter_cache_manager/test/image_cache_manager_test.dart index ce8ec599..e2390548 100644 --- a/flutter_cache_manager/test/image_cache_manager_test.dart +++ b/flutter_cache_manager/test/image_cache_manager_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; @@ -127,6 +128,42 @@ void main() { expect(progress, isNotEmpty); }); }); + + group('Test image detection', () { + test('Image without file extension should be resized', () async { + const urlWithoutExtension = 'baseflow.com/avatar/123456'; + var config = createTestConfig(); + await config.returnsFile('avatar', data: await getExampleImage()); + config.returnsCacheObject(urlWithoutExtension, 'avatar', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager + .getImageFile(urlWithoutExtension, maxWidth: 100) + .last + as FileInfo; + + var image = await result.file.readAsBytes(); + await verifySize(image, 100, 100); + }); + + test('Non image should not be resized', () async { + const urlWithImageExtension = 'baseflow.com/image.png'; + var payload = utf8.encode('{"message":"not an image"}'); + var config = createTestConfig(); + await config.returnsFile('image.png', data: payload); + config.returnsCacheObject(urlWithImageExtension, 'image.png', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager + .getImageFile(urlWithImageExtension, maxWidth: 100) + .last + as FileInfo; + + expect(await result.file.readAsBytes(), payload); + }); + }); } Future setupCacheManager() async { From 3a498ba50279af5e7137a602e125a54c986f4b7b Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 13:33:19 +0800 Subject: [PATCH 11/18] fix(image): also detect bmp by signature and wbmp by extension Flutter decodes bmp and wbmp as well, so they are resizable too. Bmp has a 'BM' signature and can be detected from the content. Wbmp starts with a two byte header that is too generic to match on, so it joins tga in the extension fallback. --- .../lib/src/cache_managers/image_cache_manager.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 52aae85a..ff25bebd 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -13,14 +13,16 @@ const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; const _imageSignatures = >[ [0xFF, 0xD8, 0xFF], // jpeg [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png + [0x42, 0x4D], // bmp [0x00, 0x00, 0x01, 0x00], // ico [0x00, 0x00, 0x02, 0x00], // cur ]; -/// Extensions of the formats that have no signature at the start of the file, -/// so the only way to recognize them is by their extension. Tga for example -/// only stores a signature in its footer. -const _extensionOnlyFormats = ['tga']; +/// Extensions of the formats that have no usable signature at the start of the +/// file, so the only way to recognize them is by their extension. Tga only +/// stores a signature in its footer, and wbmp starts with a two byte header +/// (`00 00`) that is too generic to match on. +const _extensionOnlyFormats = ['tga', 'wbmp']; mixin ImageCacheManager on BaseCacheManager { /// Returns a resized image file to fit within maxHeight and maxWidth. It From a7dac2af1dee6dd225349c09be22e3440199758e Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 13:42:26 +0800 Subject: [PATCH 12/18] fix(image): match jpeg on its two byte signature Two bytes (the SOI marker) are enough to tell jpeg apart from the other formats that are checked, which is what SDWebImage and most detectors do. A file that is detected as an image but fails to decode then errors out of getImageFile, as it already did for a mislabeled extension before this change. --- .../lib/src/cache_managers/image_cache_manager.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index ff25bebd..470a25a9 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -11,7 +11,7 @@ const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; /// Signatures (magic numbers) of the image formats that can be recognized by /// the first bytes of the file, instead of by its extension. const _imageSignatures = >[ - [0xFF, 0xD8, 0xFF], // jpeg + [0xFF, 0xD8], // jpeg [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png [0x42, 0x4D], // bmp [0x00, 0x00, 0x01, 0x00], // ico From 780d6626a605549566542a1b0d6ee4d2728d8466 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 13:51:12 +0800 Subject: [PATCH 13/18] fix(image): store the resized image with a png extension The extension that was passed to putFile came from the cached file path. For a url without an extension that path ends in the key itself, so the extension contained a slash and putFile failed to create the file. This is what the new test for an extension-less url caught. The resized image is always encoded as png anyway, so name it png instead of copying the extension of the url. --- flutter_cache_manager/CHANGELOG.md | 1 + .../lib/src/cache_managers/image_cache_manager.dart | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/flutter_cache_manager/CHANGELOG.md b/flutter_cache_manager/CHANGELOG.md index 8ac9dfb4..c2403c6a 100644 --- a/flutter_cache_manager/CHANGELOG.md +++ b/flutter_cache_manager/CHANGELOG.md @@ -1,6 +1,7 @@ ## [3.4.6] - 2026-10-19 * Detects whether a downloaded file can be resized from its content instead of from the extension of its url, so images served from extension-less urls or with a non-image `Content-Type` are resized, and responses that are no longer images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) +* Stores resized images with a `.png` extension, matching the encoding that is used, instead of reusing the extension of the url — which broke resizing for urls without an extension ## [3.4.5] - 2026-09-18 diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 470a25a9..6c841913 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -114,8 +114,6 @@ mixin ImageCacheManager on BaseCacheManager { int? maxWidth, int? maxHeight, ) async { - final originalFileName = originalFile.file.path; - final fileExtension = originalFileName.split('.').last; if (!await _isImage(originalFile.file)) { return originalFile; } @@ -152,7 +150,11 @@ mixin ImageCacheManager on BaseCacheManager { resizedFile, key: key, maxAge: maxAge, - fileExtension: fileExtension, + // The resized image is always encoded as png. The extension of the + // original file cannot be used: it is derived from the url, so it can + // be missing, or be the last segment of the path when the url has no + // extension at all. + fileExtension: 'png', ); return FileInfo( From e9eca3a04bf23edff6cce52d9111f32a72b23fac Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 14:03:13 +0800 Subject: [PATCH 14/18] fix(image): keep an animated webp out of the resizer The resizer always re-encodes the image to png, which keeps a single frame. Now that webp is recognized by its signature, an animated webp would enter the resizer and silently lose its animation. That is the same reason gif is not recognized at all. Read the flags of the VP8X chunk, which is where an extended webp marks itself as animated, and pass those files through untouched. A static webp, with or without a VP8X chunk, is still resized. --- flutter_cache_manager/CHANGELOG.md | 1 + .../cache_managers/image_cache_manager.dart | 42 ++++++++++++++----- .../test/image_cache_manager_test.dart | 29 +++++++++++++ 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/flutter_cache_manager/CHANGELOG.md b/flutter_cache_manager/CHANGELOG.md index c2403c6a..53534429 100644 --- a/flutter_cache_manager/CHANGELOG.md +++ b/flutter_cache_manager/CHANGELOG.md @@ -2,6 +2,7 @@ * Detects whether a downloaded file can be resized from its content instead of from the extension of its url, so images served from extension-less urls or with a non-image `Content-Type` are resized, and responses that are no longer images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) * Stores resized images with a `.png` extension, matching the encoding that is used, instead of reusing the extension of the url — which broke resizing for urls without an extension +* Adds webp, bmp and wbmp to the formats that are resized ([#353](https://github.com/Baseflow/flutter_cache_manager/issues/353)). An animated webp is passed through untouched, since the resized image is always encoded as png and would lose its animation, the same reason gif is not resized ## [3.4.5] - 2026-09-18 diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 6c841913..29c86809 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -10,6 +10,9 @@ const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; /// Signatures (magic numbers) of the image formats that can be recognized by /// the first bytes of the file, instead of by its extension. +/// +/// Gif is deliberately missing: the resizer re-encodes to png, which would turn +/// an animation into a single frame. const _imageSignatures = >[ [0xFF, 0xD8], // jpeg [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png @@ -87,20 +90,23 @@ mixin ImageCacheManager on BaseCacheManager { /// response can be served without a file extension, or with an extension /// that does not match the actual content, for example when it is served /// with an incorrect `Content-Type` header. + /// + /// Images that would lose something when they are re-encoded as png, such as + /// an animated gif or an animated webp, are not considered an image here, so + /// they are passed through untouched. Future _isImage(File file) async { if (!await file.exists()) { return false; } final header = await _readHeader(file); - if (_imageSignatures.any((signature) => _startsWith(header, signature))) { + if (_imageSignatures.any((signature) => _matches(header, signature))) { return true; } // WebP is a RIFF container: "RIFF" + 4 bytes of length + "WEBP". - if (header.length >= 12 && - _startsWith(header, 'RIFF'.codeUnits) && - _startsWith(header.sublist(8), 'WEBP'.codeUnits)) { - return true; + if (_matches(header, 'RIFF'.codeUnits) && + _matches(header, 'WEBP'.codeUnits, 8)) { + return !_isAnimatedWebp(header); } final extension = file.path.split('.').last.toLowerCase(); return _extensionOnlyFormats.contains(extension); @@ -191,8 +197,8 @@ mixin ImageCacheManager on BaseCacheManager { } /// Reads the first bytes of [file], up to [length] bytes, which is enough to -/// hold the longest signature that is checked. -Future> _readHeader(File file, {int length = 12}) async { +/// hold the longest signature that is checked, including the webp flags. +Future> _readHeader(File file, {int length = 30}) async { final raf = await file.open(); try { return await raf.read(length); @@ -201,19 +207,33 @@ Future> _readHeader(File file, {int length = 12}) async { } } -/// Whether [bytes] starts with [signature]. -bool _startsWith(List bytes, List signature) { - if (bytes.length < signature.length) { +/// Whether [bytes] holds [signature] at [offset]. +bool _matches(List bytes, List signature, [int offset = 0]) { + if (bytes.length < offset + signature.length) { return false; } for (var i = 0; i < signature.length; i++) { - if (bytes[i] != signature[i]) { + if (bytes[offset + i] != signature[i]) { return false; } } return true; } +/// Whether [header] belongs to a webp that contains an animation. +/// +/// An extended webp starts with a `VP8X` chunk, whose flags byte marks the file +/// as animated. Resizing re-encodes the image to png, which keeps a single +/// frame, so an animated webp is left as it is, the same way gif is. +bool _isAnimatedWebp(List header) { + // "RIFF" + size + "WEBP" + "VP8X" + chunk size, the flags follow at 20. + const flagsOffset = 20; + const animationFlag = 0x02; + return header.length > flagsOffset && + _matches(header, 'VP8X'.codeUnits, 12) && + (header[flagsOffset] & animationFlag) != 0; +} + Future _decodeImage( File file, { int? width, diff --git a/flutter_cache_manager/test/image_cache_manager_test.dart b/flutter_cache_manager/test/image_cache_manager_test.dart index e2390548..3884f292 100644 --- a/flutter_cache_manager/test/image_cache_manager_test.dart +++ b/flutter_cache_manager/test/image_cache_manager_test.dart @@ -163,6 +163,21 @@ void main() { expect(await result.file.readAsBytes(), payload); }); + + test('Animated webp should not be resized', () async { + const urlOfAnimatedWebp = 'baseflow.com/animation.webp'; + var payload = animatedWebpHeader(); + var config = createTestConfig(); + await config.returnsFile('animation.webp', data: payload); + config.returnsCacheObject(urlOfAnimatedWebp, 'animation.webp', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager.getImageFile(urlOfAnimatedWebp, maxWidth: 100).last + as FileInfo; + + expect(await result.file.readAsBytes(), payload); + }); }); } @@ -194,3 +209,17 @@ Future verifySize( Future getExampleImage() { return File('test/images/image-120.png').readAsBytes(); } + +/// The header of an animated webp: a `VP8X` chunk with the animation flag set. +/// Only the header is needed, the file is never decoded because it is not +/// resized. +Uint8List animatedWebpHeader() { + return Uint8List.fromList([ + ...'RIFF'.codeUnits, 0x1A, 0x00, 0x00, 0x00, // + ...'WEBP'.codeUnits, // + ...'VP8X'.codeUnits, 0x0A, 0x00, 0x00, 0x00, // + 0x02, // flags: animation + 0x00, 0x00, 0x00, // canvas width - 1 + 0x00, 0x00, 0x00, // canvas height - 1 + ]); +} From fe77da7140f84b086d469220086e43f18ee59f63 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Mon, 19 Oct 2026 14:36:23 +0800 Subject: [PATCH 15/18] docs(image): explain why ico and cur overlap with tga A tga without an id field and without a colour map starts with the same four bytes as an ico or a cur, so the signatures cannot be told apart. It does not affect the result, since all of them are resizable, but it looks like a mistake without the note. --- .../lib/src/cache_managers/image_cache_manager.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 29c86809..0dbe6a78 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -13,6 +13,12 @@ const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; /// /// Gif is deliberately missing: the resizer re-encodes to png, which would turn /// an animation into a single frame. +/// +/// The ico and cur values overlap with tga, which starts with the same four +/// bytes when it has no id field and no colour map (byte 2 is then the image +/// type, 1 for a colour mapped and 2 for a true colour image). Telling them +/// apart takes more than four bytes, but it does not matter here: all of them +/// are resizable, and only the answer to "is this an image" is used. const _imageSignatures = >[ [0xFF, 0xD8], // jpeg [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png From 02775e77ead9b435fc405ed6c470d351f8973012 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Tue, 27 Oct 2026 15:59:21 +0800 Subject: [PATCH 16/18] refactor(image): let the engine decide whether a file can be resized Instead of matching magic numbers, the file is offered to the image engine: ImageDescriptor.encoded + instantiateCodec report whether the file decodes at all, how many frames it has and how large it is. A file that fails to decode, or that holds an animation (gif, apng, animated webp), is returned untouched instead of erroring the stream or losing its animation. This covers every format the engine supports, including platform ones like heif, without a list to maintain. Also: - Fix shouldResize only checking maxHeight when maxWidth is null. - Fix the comment on the stored extension: it comes from the Content-Type header, not from the url; storing as png was already correct since the resized image is png-encoded. - Deprecate supportedFileNames, which no longer reflects what gets resized. Tests: an image served with a generic content type is resized and stored as .png, static webp and bmp are resized, a file that fails to decode is returned untouched, and animated gif, apng and webp are passed through. --- flutter_cache_manager/CHANGELOG.md | 7 +- .../cache_managers/image_cache_manager.dart | 157 +++++++----------- .../test/image_cache_manager_test.dart | 106 ++++++++++-- .../test/images/animated-120.apng | Bin 0 -> 1675 bytes .../test/images/animated-120.gif | Bin 0 -> 8337 bytes .../test/images/animated-120.webp | Bin 0 -> 3352 bytes .../test/images/image-120.bmp | Bin 0 -> 43254 bytes .../test/images/image-120.webp | Bin 0 -> 11920 bytes 8 files changed, 154 insertions(+), 116 deletions(-) create mode 100644 flutter_cache_manager/test/images/animated-120.apng create mode 100644 flutter_cache_manager/test/images/animated-120.gif create mode 100644 flutter_cache_manager/test/images/animated-120.webp create mode 100644 flutter_cache_manager/test/images/image-120.bmp create mode 100644 flutter_cache_manager/test/images/image-120.webp diff --git a/flutter_cache_manager/CHANGELOG.md b/flutter_cache_manager/CHANGELOG.md index 53534429..cde14705 100644 --- a/flutter_cache_manager/CHANGELOG.md +++ b/flutter_cache_manager/CHANGELOG.md @@ -1,8 +1,9 @@ ## [3.4.6] - 2026-10-19 -* Detects whether a downloaded file can be resized from its content instead of from the extension of its url, so images served from extension-less urls or with a non-image `Content-Type` are resized, and responses that are no longer images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) -* Stores resized images with a `.png` extension, matching the encoding that is used, instead of reusing the extension of the url — which broke resizing for urls without an extension -* Adds webp, bmp and wbmp to the formats that are resized ([#353](https://github.com/Baseflow/flutter_cache_manager/issues/353)). An animated webp is passed through untouched, since the resized image is always encoded as png and would lose its animation, the same reason gif is not resized +* Detects whether a downloaded file can be resized by letting the image engine decode it, instead of from the extension of the url. Images served with a generic `Content-Type` such as `application/octet-stream` are now resized, and files that are not images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) +* Detects the image format from the file content, so every format the engine supports is resized, including webp and bmp ([#353](https://github.com/Baseflow/flutter_cache_manager/issues/353)). Animated images (gif, apng, animated webp) are passed through untouched, since the resized image is always encoded as png and would lose its animation +* Stores resized images with a `.png` extension, matching the encoding that is used, instead of reusing the extension derived from the `Content-Type` header +* Deprecates `supportedFileNames`, which is no longer used now that detection is content based ## [3.4.5] - 2026-09-18 diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 0dbe6a78..c59135c1 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -6,32 +6,17 @@ import 'package:file/file.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; - -/// Signatures (magic numbers) of the image formats that can be recognized by -/// the first bytes of the file, instead of by its extension. -/// -/// Gif is deliberately missing: the resizer re-encodes to png, which would turn -/// an animation into a single frame. +/// Formats that used to be recognized by the extension of the file. /// -/// The ico and cur values overlap with tga, which starts with the same four -/// bytes when it has no id field and no colour map (byte 2 is then the image -/// type, 1 for a colour mapped and 2 for a true colour image). Telling them -/// apart takes more than four bytes, but it does not matter here: all of them -/// are resizable, and only the answer to "is this an image" is used. -const _imageSignatures = >[ - [0xFF, 0xD8], // jpeg - [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // png - [0x42, 0x4D], // bmp - [0x00, 0x00, 0x01, 0x00], // ico - [0x00, 0x00, 0x02, 0x00], // cur -]; - -/// Extensions of the formats that have no usable signature at the start of the -/// file, so the only way to recognize them is by their extension. Tga only -/// stores a signature in its footer, and wbmp starts with a two byte header -/// (`00 00`) that is too generic to match on. -const _extensionOnlyFormats = ['tga', 'wbmp']; +/// Whether a file can be resized is now decided by the image engine, based on +/// the content of the file, so this list no longer reflects which files are +/// resized. It is kept only for backwards compatibility. +@Deprecated( + 'Image formats are detected from the file content instead of the file ' + 'extension. This list is no longer used and will be removed in a future ' + 'version.', +) +const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'cur', 'ico']; mixin ImageCacheManager on BaseCacheManager { /// Returns a resized image file to fit within maxHeight and maxWidth. It @@ -91,33 +76,6 @@ mixin ImageCacheManager on BaseCacheManager { _runningResizes.remove(resizedKey); } - /// Whether [file] holds an image that can be decoded and resized, based on - /// the content of the file rather than on the extension of its url. A - /// response can be served without a file extension, or with an extension - /// that does not match the actual content, for example when it is served - /// with an incorrect `Content-Type` header. - /// - /// Images that would lose something when they are re-encoded as png, such as - /// an animated gif or an animated webp, are not considered an image here, so - /// they are passed through untouched. - Future _isImage(File file) async { - if (!await file.exists()) { - return false; - } - - final header = await _readHeader(file); - if (_imageSignatures.any((signature) => _matches(header, signature))) { - return true; - } - // WebP is a RIFF container: "RIFF" + 4 bytes of length + "WEBP". - if (_matches(header, 'RIFF'.codeUnits) && - _matches(header, 'WEBP'.codeUnits, 8)) { - return !_isAnimatedWebp(header); - } - final extension = file.path.split('.').last.toLowerCase(); - return _extensionOnlyFormats.contains(extension); - } - final Map> _runningResizes = {}; Future _resizeImageFile( @@ -126,25 +84,33 @@ mixin ImageCacheManager on BaseCacheManager { int? maxWidth, int? maxHeight, ) async { - if (!await _isImage(originalFile.file)) { + final info = await _imageInfo(originalFile.file); + if (info == null) { + // The engine cannot decode this file, so it is not a resizable image. + // The original file is returned untouched instead of erroring the + // stream. return originalFile; } + final (frameCount, imageWidth, imageHeight) = info; - final image = await _decodeImage(originalFile.file); + // Resizing re-encodes the image as png, which keeps a single frame, so + // animated images (gif, apng, animated webp, ...) are passed through + // untouched. + if (frameCount > 1) { + return originalFile; + } - final shouldResize = maxWidth != null - ? image.width > maxWidth - : false || maxHeight != null - ? image.height > maxHeight - : false; + final shouldResize = + (maxWidth != null && imageWidth > maxWidth) || + (maxHeight != null && imageHeight > maxHeight); if (!shouldResize) return originalFile; if (maxWidth != null && maxHeight != null) { - final resizeFactorWidth = image.width / maxWidth; - final resizeFactorHeight = image.height / maxHeight; + final resizeFactorWidth = imageWidth / maxWidth; + final resizeFactorHeight = imageHeight / maxHeight; final resizeFactor = max(resizeFactorHeight, resizeFactorWidth); - maxWidth = (image.width / resizeFactor).round(); - maxHeight = (image.height / resizeFactor).round(); + maxWidth = (imageWidth / resizeFactor).round(); + maxHeight = (imageHeight / resizeFactor).round(); } final resized = await _decodeImage( @@ -162,10 +128,11 @@ mixin ImageCacheManager on BaseCacheManager { resizedFile, key: key, maxAge: maxAge, - // The resized image is always encoded as png. The extension of the - // original file cannot be used: it is derived from the url, so it can - // be missing, or be the last segment of the path when the url has no - // extension at all. + // The resized image is always encoded as png, so it is stored with a + // png extension. The extension of the original file cannot be reused: + // it comes from the Content-Type header of the response, which can be + // something generic like `application/octet-stream` instead of the + // format the image is actually encoded in. fileExtension: 'png', ); @@ -202,42 +169,32 @@ mixin ImageCacheManager on BaseCacheManager { } } -/// Reads the first bytes of [file], up to [length] bytes, which is enough to -/// hold the longest signature that is checked, including the webp flags. -Future> _readHeader(File file, {int length = 30}) async { - final raf = await file.open(); +/// Describes the image in [file], or `null` when the engine cannot decode it. +/// +/// Whether a file is a resizable image is decided entirely by the engine: it +/// knows every format Flutter can decode, including platform specific ones +/// like heif, so there is no list of signatures or extensions to maintain. +/// The returned record holds the frame count of the image, which is how +/// animated images are recognized, and its width and height. +Future<(int frameCount, int width, int height)?> _imageInfo(File file) async { + if (!await file.exists()) { + return null; + } + final bytes = await file.readAsBytes(); + final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + ui.ImageDescriptor? descriptor; + ui.Codec? codec; try { - return await raf.read(length); + descriptor = await ui.ImageDescriptor.encoded(buffer); + codec = await descriptor.instantiateCodec(); + return (codec.frameCount, descriptor.width, descriptor.height); + } on Object { + return null; } finally { - await raf.close(); - } -} - -/// Whether [bytes] holds [signature] at [offset]. -bool _matches(List bytes, List signature, [int offset = 0]) { - if (bytes.length < offset + signature.length) { - return false; - } - for (var i = 0; i < signature.length; i++) { - if (bytes[offset + i] != signature[i]) { - return false; - } + codec?.dispose(); + descriptor?.dispose(); + buffer.dispose(); } - return true; -} - -/// Whether [header] belongs to a webp that contains an animation. -/// -/// An extended webp starts with a `VP8X` chunk, whose flags byte marks the file -/// as animated. Resizing re-encodes the image to png, which keeps a single -/// frame, so an animated webp is left as it is, the same way gif is. -bool _isAnimatedWebp(List header) { - // "RIFF" + size + "WEBP" + "VP8X" + chunk size, the flags follow at 20. - const flagsOffset = 20; - const animationFlag = 0x02; - return header.length > flagsOffset && - _matches(header, 'VP8X'.codeUnits, 12) && - (header[flagsOffset] & animationFlag) != 0; } Future _decodeImage( diff --git a/flutter_cache_manager/test/image_cache_manager_test.dart b/flutter_cache_manager/test/image_cache_manager_test.dart index 3884f292..6d6867bc 100644 --- a/flutter_cache_manager/test/image_cache_manager_test.dart +++ b/flutter_cache_manager/test/image_cache_manager_test.dart @@ -147,6 +147,62 @@ void main() { await verifySize(image, 100, 100); }); + test('Image with a non-image extension should be resized', () async { + // The stored file's extension does not come from the url, but from the + // Content-Type header of the response. A generic type such as + // application/octet-stream stores the file as .bin, even when the + // payload is an image. + const urlWithBinaryExtension = 'baseflow.com/image.bin'; + var config = createTestConfig(); + await config.returnsFile('image.bin', data: await getExampleImage()); + config.returnsCacheObject(urlWithBinaryExtension, 'image.bin', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager + .getImageFile(urlWithBinaryExtension, maxWidth: 100) + .last + as FileInfo; + + var image = await result.file.readAsBytes(); + await verifySize(image, 100, 100); + expect(result.file.path.endsWith('.png'), isTrue); + }); + + test('Static webp should be resized', () async { + const urlWithWebpExtension = 'baseflow.com/image.webp'; + var config = createTestConfig(); + await config.returnsFile('image.webp', data: await getExampleWebp()); + config.returnsCacheObject(urlWithWebpExtension, 'image.webp', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager + .getImageFile(urlWithWebpExtension, maxWidth: 100) + .last + as FileInfo; + + var image = await result.file.readAsBytes(); + await verifySize(image, 100, 100); + }); + + test('Bmp should be resized', () async { + const urlWithBmpExtension = 'baseflow.com/image.bmp'; + var config = createTestConfig(); + await config.returnsFile('image.bmp', data: await getExampleBmp()); + config.returnsCacheObject(urlWithBmpExtension, 'image.bmp', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager + .getImageFile(urlWithBmpExtension, maxWidth: 100) + .last + as FileInfo; + + var image = await result.file.readAsBytes(); + await verifySize(image, 100, 100); + }); + test('Non image should not be resized', () async { const urlWithImageExtension = 'baseflow.com/image.png'; var payload = utf8.encode('{"message":"not an image"}'); @@ -164,9 +220,24 @@ void main() { expect(await result.file.readAsBytes(), payload); }); + test('Animated gif should not be resized', () async { + const urlOfAnimatedGif = 'baseflow.com/animation.gif'; + var payload = await File('test/images/animated-120.gif').readAsBytes(); + var config = createTestConfig(); + await config.returnsFile('animation.gif', data: payload); + config.returnsCacheObject(urlOfAnimatedGif, 'animation.gif', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager.getImageFile(urlOfAnimatedGif, maxWidth: 100).last + as FileInfo; + + expect(await result.file.readAsBytes(), payload); + }); + test('Animated webp should not be resized', () async { const urlOfAnimatedWebp = 'baseflow.com/animation.webp'; - var payload = animatedWebpHeader(); + var payload = await File('test/images/animated-120.webp').readAsBytes(); var config = createTestConfig(); await config.returnsFile('animation.webp', data: payload); config.returnsCacheObject(urlOfAnimatedWebp, 'animation.webp', validTill); @@ -178,6 +249,21 @@ void main() { expect(await result.file.readAsBytes(), payload); }); + + test('Animated png should not be resized', () async { + const urlOfAnimatedPng = 'baseflow.com/animation.png'; + var payload = await File('test/images/animated-120.apng').readAsBytes(); + var config = createTestConfig(); + await config.returnsFile('animation.png', data: payload); + config.returnsCacheObject(urlOfAnimatedPng, 'animation.png', validTill); + + var cacheManager = TestCacheManager(config); + var result = + await cacheManager.getImageFile(urlOfAnimatedPng, maxWidth: 100).last + as FileInfo; + + expect(await result.file.readAsBytes(), payload); + }); }); } @@ -210,16 +296,10 @@ Future getExampleImage() { return File('test/images/image-120.png').readAsBytes(); } -/// The header of an animated webp: a `VP8X` chunk with the animation flag set. -/// Only the header is needed, the file is never decoded because it is not -/// resized. -Uint8List animatedWebpHeader() { - return Uint8List.fromList([ - ...'RIFF'.codeUnits, 0x1A, 0x00, 0x00, 0x00, // - ...'WEBP'.codeUnits, // - ...'VP8X'.codeUnits, 0x0A, 0x00, 0x00, 0x00, // - 0x02, // flags: animation - 0x00, 0x00, 0x00, // canvas width - 1 - 0x00, 0x00, 0x00, // canvas height - 1 - ]); +Future getExampleWebp() { + return File('test/images/image-120.webp').readAsBytes(); +} + +Future getExampleBmp() { + return File('test/images/image-120.bmp').readAsBytes(); } diff --git a/flutter_cache_manager/test/images/animated-120.apng b/flutter_cache_manager/test/images/animated-120.apng new file mode 100644 index 0000000000000000000000000000000000000000..e4016a7ee365bf69454f8c0e788b979c02a685b2 GIT binary patch literal 1675 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^bj>O~;A0W*P#0=-&?m7UZq|zW_ zAR({@5CaSt8GwfM+&TDzfr0Ubr;B4q#hkZy9rJEG2rxK$p7_82&7E2 z;Wi=;9~v9qSKMm8z|2(t*ig<%o}=c`PaUWFm%(3Fw!g1*w>?JS|v$ zW+Io(+koX~H(PzUa(Ir_^;HKh^B4ZASa6#+iQax@dyvgu^~1H? z8&==9-*}+Ta&I5&?dSU^^Mbv)!~f~8&`SC6%?Asaj&sVW7tA>TW!zc*X+HDR?}et) zU{QaLJ%4{~t^H?x^TBeKd%IYppZ^n+{$P=&DdTNg ziem^+1rsoJ+;Q}CHWXk9tUto|c=9aiwyXCyFmg8SJ{_8^(pD*@DqUlHtcvgOw{6u7 z)wxWEg(c=4_`uSpZeY{+kdwzhp@POj@0qVu+3ou~v$D2l@~&C)zx?NX@$$jc1NDs6 zTqU9(v=d%l5Iyjp(Yv+pzt~>p^5^euxhlR-5@)a!vq;^kox&$la4RVMhRxLjD^JHP zEDmK{u3y-CUYz&NrahrX=`tG{8*i^m5!_Tb$3njkzIu-NM3q z90}G8JDzH#ot#s6fcNz5ho6r!tLqoGoeO23v*V1UnZIqprJ(c^G8PY7o}MeZQ}JM~ z_dJF>vzGI`uURd`SMxl3bHM}a``vBK^=HNJKRgijL-$tQbRwgn`L4R5d*Rjn_i8U9 zB@a+xf+czUvixxrm^|)7lLrepdGs|Kh_D1ooVB%e@AjW%>BJ=QCpLC#pNqS{*v%uq zv$Nly-SGE}T*7xXdZh_?lKXC5&9LxWUF{#A`~SB|zKwfX&z~V$&?w(lD;4#?bA!Jl zw~getyo`ER@k4tb?_>gzKbgc2%kFi4(6mGOJ4cL2`4zp!?Ynj;e>=*&{n3u_@?*@G zyV>%(=iJk-Ufd|$#hTYW_ul5+$&JBXEP37Y?iv49Zk#LxN*a$EZa;c|fAM`~J_}v+ zoFLAANB!^V{A=GknEkv#Ii#EAUHAU>{x|(Bep+HX^sBo1-}SNjX-e(TFY7x0rkBl6 zLvDwDQP=r*J?wFeKTp{;b}PrnNyIuT)q!WwhzYcASe-b-Y x(KLVd*%g)XC1Gc!@8^d7>moKwemM3{e$MB@tNmpi3BW|a;OXk;vd$@?2>`Um%76d> literal 0 HcmV?d00001 diff --git a/flutter_cache_manager/test/images/animated-120.gif b/flutter_cache_manager/test/images/animated-120.gif new file mode 100644 index 0000000000000000000000000000000000000000..3d03d817807a5b777258405cfd0e27a20fd1ff86 GIT binary patch literal 8337 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 Dre7s~ literal 0 HcmV?d00001 diff --git a/flutter_cache_manager/test/images/animated-120.webp b/flutter_cache_manager/test/images/animated-120.webp new file mode 100644 index 0000000000000000000000000000000000000000..f5b57aa776a467a299cf617150d580806e5e2eef GIT binary patch literal 3352 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 IY_IZv0OxeW_5c6? literal 0 HcmV?d00001 diff --git a/flutter_cache_manager/test/images/image-120.bmp b/flutter_cache_manager/test/images/image-120.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1b93207ef76c63d6b0c6657b1b7e7d8b0cad8bd0 GIT binary patch literal 43254 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 literal 0 HcmV?d00001 From 2b58ba86a099bf41dcbd11a044e771c4c4172b97 Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Thu, 29 Oct 2026 22:43:51 +0800 Subject: [PATCH 17/18] fix(image): read the size from the first frame and prefer the file path ImageDescriptor.width and height throw on web, so the size is taken from the first frame instead. On IO the buffer is created from the path so the bytes are not copied into memory for files that turn out not to be images. The CHANGELOG wording now refers to the Content-Type header and merges the duplicated bullets. --- flutter_cache_manager/CHANGELOG.md | 3 +- .../cache_managers/image_cache_manager.dart | 34 +++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/flutter_cache_manager/CHANGELOG.md b/flutter_cache_manager/CHANGELOG.md index 8d0d85b9..639057ee 100644 --- a/flutter_cache_manager/CHANGELOG.md +++ b/flutter_cache_manager/CHANGELOG.md @@ -1,7 +1,6 @@ ## [3.4.7] - 2026-10-27 -* Detects whether a downloaded file can be resized by letting the image engine decode it, instead of from the extension of the url. Images served with a generic `Content-Type` such as `application/octet-stream` are now resized, and files that are not images are returned untouched ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) -* Detects the image format from the file content, so every format the engine supports is resized, including webp and bmp ([#353](https://github.com/Baseflow/flutter_cache_manager/issues/353)). Animated images (gif, apng, animated webp) are passed through untouched, since the resized image is always encoded as png and would lose its animation +* Detects whether a downloaded file can be resized by letting the image engine decode it, instead of from the file extension derived from the `Content-Type` header of the response. Images served with a generic `Content-Type` such as `application/octet-stream` are now resized, files that are not images are returned untouched, and every format the engine supports is resized, including webp and bmp ([#353](https://github.com/Baseflow/flutter_cache_manager/issues/353)). Animated images (gif, apng, animated webp) are passed through untouched, since the resized image is always encoded as png and would lose its animation ([#475](https://github.com/Baseflow/flutter_cache_manager/pull/475)) * Stores resized images with a `.png` extension, matching the encoding that is used, instead of reusing the extension derived from the `Content-Type` header * Deprecates `supportedFileNames`, which is no longer used now that detection is content based diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index c59135c1..976c8f01 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:file/file.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; @@ -180,14 +181,22 @@ Future<(int frameCount, int width, int height)?> _imageInfo(File file) async { if (!await file.exists()) { return null; } - final bytes = await file.readAsBytes(); - final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + final buffer = await _bufferFor(file); ui.ImageDescriptor? descriptor; ui.Codec? codec; try { descriptor = await ui.ImageDescriptor.encoded(buffer); codec = await descriptor.instantiateCodec(); - return (codec.frameCount, descriptor.width, descriptor.height); + // `descriptor.width` and `descriptor.height` are not supported on web + // (they throw), so the size is taken from the first frame instead. This + // fully decodes that frame, which costs the same as the decode the + // resize performs anyway. + final frame = await codec.getNextFrame(); + try { + return (codec.frameCount, frame.image.width, frame.image.height); + } finally { + frame.image.dispose(); + } } on Object { return null; } finally { @@ -197,6 +206,25 @@ Future<(int frameCount, int width, int height)?> _imageInfo(File file) async { } } +/// Creates the buffer for [file]. +/// +/// On web there is no file path to read from, so the bytes are read into +/// memory. On IO the path is preferred, so the bytes are not copied into +/// memory before we know whether the file is an image at all (it can be a +/// large video or pdf). Tests run against a mocked file system where the +/// path does not exist on the real file system, so reading the bytes is the +/// fallback there. +Future _bufferFor(File file) async { + if (kIsWeb) { + return ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()); + } + try { + return await ui.ImmutableBuffer.fromFilePath(file.path); + } on Object { + return ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()); + } +} + Future _decodeImage( File file, { int? width, From fea6c33806dab97091a94ad5252888198860675c Mon Sep 17 00:00:00 2001 From: afer <791143125@qq.com> Date: Fri, 30 Oct 2026 00:17:29 +0800 Subject: [PATCH 18/18] test(image): embed the fixture images so the tests also run on web The test file loaded its fixtures with dart:io, so `flutter test --platform chrome` failed during setup and the web resize path was never exercised. The images are now base64 constants in test_images.dart, which keeps the bytes in the test bundle and works in the browser as well. The fixtures are regenerated as plain 120x120 images (8 colour png, 1 bpp bmp, 3 frame gif/apng/webp) so they stay small: 5.7 KB in total instead of 84 KB. --- .../test/helpers/mock_file_service.dart | 5 +- .../test/image_cache_manager_test.dart | 37 ++--- .../test/images/animated-120.apng | Bin 1675 -> 0 bytes .../test/images/animated-120.gif | Bin 8337 -> 0 bytes .../test/images/animated-120.webp | Bin 3352 -> 0 bytes .../test/images/image-120.bmp | Bin 43254 -> 0 bytes .../test/images/image-120.png | Bin 17100 -> 0 bytes .../test/images/image-120.webp | Bin 11920 -> 0 bytes flutter_cache_manager/test/test_images.dart | 138 ++++++++++++++++++ 9 files changed, 157 insertions(+), 23 deletions(-) delete mode 100644 flutter_cache_manager/test/images/animated-120.apng delete mode 100644 flutter_cache_manager/test/images/animated-120.gif delete mode 100644 flutter_cache_manager/test/images/animated-120.webp delete mode 100644 flutter_cache_manager/test/images/image-120.bmp delete mode 100644 flutter_cache_manager/test/images/image-120.png delete mode 100644 flutter_cache_manager/test/images/image-120.webp create mode 100644 flutter_cache_manager/test/test_images.dart diff --git a/flutter_cache_manager/test/helpers/mock_file_service.dart b/flutter_cache_manager/test/helpers/mock_file_service.dart index 9c191dbf..39aec863 100644 --- a/flutter_cache_manager/test/helpers/mock_file_service.dart +++ b/flutter_cache_manager/test/helpers/mock_file_service.dart @@ -1,10 +1,11 @@ -import 'dart:io'; +import 'dart:convert'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/src/web/file_service.dart'; import 'package:mockito/mockito.dart'; import '../mock.mocks.dart'; +import '../test_images.dart'; class MockFileService extends MockFileServiceBase { MockFileService._(); @@ -26,7 +27,7 @@ class MockFileService extends MockFileServiceBase { class TestResponse extends FileServiceResponse { @override Stream> get content async* { - var bytes = await File('test/images/image-120.png').readAsBytes(); + var bytes = base64Decode(imagePng120); var length = bytes.length; var firstPart = (length / 2).floor(); yield bytes.sublist(0, firstPart); diff --git a/flutter_cache_manager/test/image_cache_manager_test.dart b/flutter_cache_manager/test/image_cache_manager_test.dart index 6d6867bc..0e1593c0 100644 --- a/flutter_cache_manager/test/image_cache_manager_test.dart +++ b/flutter_cache_manager/test/image_cache_manager_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; @@ -10,6 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'cache_manager_test.dart'; import 'helpers/config_extensions.dart'; import 'helpers/test_configuration.dart'; +import 'test_images.dart'; const fileName = 'test.jpg'; const fileUrl = 'baseflow.com/test'; @@ -25,7 +25,7 @@ void main() { group('Test image resizing', () { test('Test original image size', () async { - final bytes = await getExampleImage(); + final bytes = exampleImage; await verifySize(bytes, 120, 120); }); @@ -133,7 +133,7 @@ void main() { test('Image without file extension should be resized', () async { const urlWithoutExtension = 'baseflow.com/avatar/123456'; var config = createTestConfig(); - await config.returnsFile('avatar', data: await getExampleImage()); + await config.returnsFile('avatar', data: exampleImage); config.returnsCacheObject(urlWithoutExtension, 'avatar', validTill); var cacheManager = TestCacheManager(config); @@ -154,7 +154,7 @@ void main() { // payload is an image. const urlWithBinaryExtension = 'baseflow.com/image.bin'; var config = createTestConfig(); - await config.returnsFile('image.bin', data: await getExampleImage()); + await config.returnsFile('image.bin', data: exampleImage); config.returnsCacheObject(urlWithBinaryExtension, 'image.bin', validTill); var cacheManager = TestCacheManager(config); @@ -172,7 +172,7 @@ void main() { test('Static webp should be resized', () async { const urlWithWebpExtension = 'baseflow.com/image.webp'; var config = createTestConfig(); - await config.returnsFile('image.webp', data: await getExampleWebp()); + await config.returnsFile('image.webp', data: exampleWebp); config.returnsCacheObject(urlWithWebpExtension, 'image.webp', validTill); var cacheManager = TestCacheManager(config); @@ -189,7 +189,7 @@ void main() { test('Bmp should be resized', () async { const urlWithBmpExtension = 'baseflow.com/image.bmp'; var config = createTestConfig(); - await config.returnsFile('image.bmp', data: await getExampleBmp()); + await config.returnsFile('image.bmp', data: exampleBmp); config.returnsCacheObject(urlWithBmpExtension, 'image.bmp', validTill); var cacheManager = TestCacheManager(config); @@ -222,7 +222,7 @@ void main() { test('Animated gif should not be resized', () async { const urlOfAnimatedGif = 'baseflow.com/animation.gif'; - var payload = await File('test/images/animated-120.gif').readAsBytes(); + var payload = animatedGif; var config = createTestConfig(); await config.returnsFile('animation.gif', data: payload); config.returnsCacheObject(urlOfAnimatedGif, 'animation.gif', validTill); @@ -237,7 +237,7 @@ void main() { test('Animated webp should not be resized', () async { const urlOfAnimatedWebp = 'baseflow.com/animation.webp'; - var payload = await File('test/images/animated-120.webp').readAsBytes(); + var payload = animatedWebp; var config = createTestConfig(); await config.returnsFile('animation.webp', data: payload); config.returnsCacheObject(urlOfAnimatedWebp, 'animation.webp', validTill); @@ -252,7 +252,7 @@ void main() { test('Animated png should not be resized', () async { const urlOfAnimatedPng = 'baseflow.com/animation.png'; - var payload = await File('test/images/animated-120.apng').readAsBytes(); + var payload = animatedApng; var config = createTestConfig(); await config.returnsFile('animation.png', data: payload); config.returnsCacheObject(urlOfAnimatedPng, 'animation.png', validTill); @@ -274,7 +274,7 @@ Future setupCacheManager() async { Future setupConfig({String? cacheKey}) async { var validTill = DateTime.now().add(const Duration(days: 1)); var config = createTestConfig(); - await config.returnsFile(fileName, data: await getExampleImage()); + await config.returnsFile(fileName, data: exampleImage); config.returnsCacheObject(fileUrl, fileName, validTill, key: cacheKey); return config; } @@ -292,14 +292,9 @@ Future verifySize( expect(height, expectedHeight); } -Future getExampleImage() { - return File('test/images/image-120.png').readAsBytes(); -} - -Future getExampleWebp() { - return File('test/images/image-120.webp').readAsBytes(); -} - -Future getExampleBmp() { - return File('test/images/image-120.bmp').readAsBytes(); -} +final Uint8List exampleImage = base64Decode(imagePng120); +final Uint8List exampleWebp = base64Decode(imageWebp120); +final Uint8List exampleBmp = base64Decode(imageBmp120); +final Uint8List animatedGif = base64Decode(animatedGif120); +final Uint8List animatedWebp = base64Decode(animatedWebp120); +final Uint8List animatedApng = base64Decode(animatedApng120); diff --git a/flutter_cache_manager/test/images/animated-120.apng b/flutter_cache_manager/test/images/animated-120.apng deleted file mode 100644 index e4016a7ee365bf69454f8c0e788b979c02a685b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1675 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^bj>O~;A0W*P#0=-&?m7UZq|zW_ zAR({@5CaSt8GwfM+&TDzfr0Ubr;B4q#hkZy9rJEG2rxK$p7_82&7E2 z;Wi=;9~v9qSKMm8z|2(t*ig<%o}=c`PaUWFm%(3Fw!g1*w>?JS|v$ zW+Io(+koX~H(PzUa(Ir_^;HKh^B4ZASa6#+iQax@dyvgu^~1H? z8&==9-*}+Ta&I5&?dSU^^Mbv)!~f~8&`SC6%?Asaj&sVW7tA>TW!zc*X+HDR?}et) zU{QaLJ%4{~t^H?x^TBeKd%IYppZ^n+{$P=&DdTNg ziem^+1rsoJ+;Q}CHWXk9tUto|c=9aiwyXCyFmg8SJ{_8^(pD*@DqUlHtcvgOw{6u7 z)wxWEg(c=4_`uSpZeY{+kdwzhp@POj@0qVu+3ou~v$D2l@~&C)zx?NX@$$jc1NDs6 zTqU9(v=d%l5Iyjp(Yv+pzt~>p^5^euxhlR-5@)a!vq;^kox&$la4RVMhRxLjD^JHP zEDmK{u3y-CUYz&NrahrX=`tG{8*i^m5!_Tb$3njkzIu-NM3q z90}G8JDzH#ot#s6fcNz5ho6r!tLqoGoeO23v*V1UnZIqprJ(c^G8PY7o}MeZQ}JM~ z_dJF>vzGI`uURd`SMxl3bHM}a``vBK^=HNJKRgijL-$tQbRwgn`L4R5d*Rjn_i8U9 zB@a+xf+czUvixxrm^|)7lLrepdGs|Kh_D1ooVB%e@AjW%>BJ=QCpLC#pNqS{*v%uq zv$Nly-SGE}T*7xXdZh_?lKXC5&9LxWUF{#A`~SB|zKwfX&z~V$&?w(lD;4#?bA!Jl zw~getyo`ER@k4tb?_>gzKbgc2%kFi4(6mGOJ4cL2`4zp!?Ynj;e>=*&{n3u_@?*@G zyV>%(=iJk-Ufd|$#hTYW_ul5+$&JBXEP37Y?iv49Zk#LxN*a$EZa;c|fAM`~J_}v+ zoFLAANB!^V{A=GknEkv#Ii#EAUHAU>{x|(Bep+HX^sBo1-}SNjX-e(TFY7x0rkBl6 zLvDwDQP=r*J?wFeKTp{;b}PrnNyIuT)q!WwhzYcASe-b-Y x(KLVd*%g)XC1Gc!@8^d7>moKwemM3{e$MB@tNmpi3BW|a;OXk;vd$@?2>`Um%76d> diff --git a/flutter_cache_manager/test/images/animated-120.gif b/flutter_cache_manager/test/images/animated-120.gif deleted file mode 100644 index 3d03d817807a5b777258405cfd0e27a20fd1ff86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8337 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 Dre7s~ diff --git a/flutter_cache_manager/test/images/animated-120.webp b/flutter_cache_manager/test/images/animated-120.webp deleted file mode 100644 index f5b57aa776a467a299cf617150d580806e5e2eef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3352 zcmciCc{J4P8vyX{%*cc+CA(%wsAx#ChEdkaaw$twh^~=BWtV0wC0QC#S+XW6qPq6P zM7C@rvTqaF4KtP*X3Ttl?(KB$AHVba=bn3h?|I+nInO!o^ZfUGY|Kqeg!lo#-uR@o zowb$&F8~00wqM|1*U-}3f_uCAc?>NrOm=eps(${s0szqMJ{c+u0J`=88IZ$4KoFqs zrIBLxLT-PKys*}dF#ZQ_=Flw|%Sjhc(fLI5o(5PzKXePT(Y%?VN`-X8HsC1;GNE}6 zzCo%(xD!uH`F zRvW9|0iK<|5SZ7zWLhUkv0o&W%z(7tDU+czr5~@RB^9ySm6Fc5n5IKkvU2@JSQyOD z+P!-%25IS=KBL|FGeiRbXGRsQ_?GyW5wq(wCq$M*TPb!^gq)6Ow9 zQdxvCi{inBiL}r+6X)g{J_P3q^=pf#u0tm+w;~RW^W2Rc8=L6kt$0$4!Kh{wqmp`Y z0yJxU#JUz-{;Sq2gZF`~-cW`FV_)61^a+Nn(z_Y{j|6!!gY}D^QOg*-T=p@Ltzm1? z9T02w{G7i^<2erzp?irMQZGAmBVGl)FhLW{>B@3)9?`;R(#08`2lvL#~oJVamy=_`GQ&pOgpdt8MR@DEBQ> zsvc&;aW0wA0s`mkhCnd^A+kaPNa6UY8<+b0Tn*tpF)X=?ro?AX zg+j~i5TaBn+KEUSrp2XFZ~dbuZa1g!1p7QN+%MWU2$gg8>_ zi0!FLr*Tx9gTXGZi9TPvm#mZ%2(|G#A5@GtkAUJwIeQPW=Q1Z>KCJOl4Hh?Kz1Sro zgr}PYNh6Mx?<{HeChsNg_dy&OuFC(KhIyBy%|h9q6H?BM$lyOABo}pCdjJ6b^dS1b zJpc^PTATH4GXW7)1$Xa0shsjdMepudGzb2Rj(;J4JMu3K`9~xE)Gl#byEXB@wL?8J ziwLd;fPts#(QxXn8wRg%CfBgp5RNk!J(`ffe|5YJ><0PSJ>7~kNV48eTrsX9pn$oj z3mUdO$(^v2%3aqv0H=)~kE_#HVNTII)?gdNmACW6&!E?F!UJN#d{1DfR_6H)xP)F9 zO=j-FG_14mYd`Z6jer(-j2wXpre6+qED<@EXYGQ<3&4pQt1SVcygVVvGMXlk)eri= z={9t6O3Krst%V@jS2|fQ!vyeKNF!B>`(E6n`p((K;BCfykOLi{nw*ilshszEX(ZLg99Y7^ZboYb1%Y(Sl$nNOSFT+?O<%4ulT`9x;%da z{b6bE*dr-4ihiOkfb^k!a>&i6v(}4mIZ#PGpPTI}seR?l^Ym{ZozbOTIfk#O4ew|q zYdoZ?aBo zO7BK74H{^Y8m~C~?pgiWDP}#0T*a*U(#2g{)YMUj^KTCmqoh&JJe%1EM)~}AlsFOb z_`LQyUvOHBwzz~Rsj8qSGOMce&!4(oy-;1^Fh#_Nm!YQ_D>uCYdgSI+R*`(#*%wpg zZ+qNHDPChdY3-A>QaLpoFIP_+x^>HO`smFw+<+--ShTacVZVfPTefr{JB=g$xmQSX zJ)}FX#eUO~agV%8DqRSTp^;uud6Btp2}MT~>#zF0Inbacx)K*EMES%@3y{U6XPXuY zrg6>#L>(h&aIdF()*^V}gbZxg>pp*PzIU407!y6yCr3d%6A_x4btd{F?{o8);w0q< z7nt5zT4lnW0(XjtB}i#_yU6e0NukEFD|hhy=AJa3=N_rlCjO< z7i3bqL3a5uHgvKf@G}2MfL~30WeHN49lxsocmC5MyKRTQ zN&M}QdWt#JP$oX?FNnBRkuLkP#n8xXakMUg2d03Ycy?qjeA>VT5mvJ>tGP*7N4*n7 z^9nc}rC(bMWN?JIgSgYUKXG?)SDI1@Kah{JTGvS-&dr{4fhd#3uwlqHJI!BF)QW8Q z)8+RQ>U+38Oc86qt`&^u0pkF{W*kBea6E{_i0^7Z|#$&|ggRUd_YYhz@d0$ZQs zO3Jq@C9=YYZH25`>*S{;9ZLBwoTC{(EoIocJJUVA9^pm9zX=gz(epbmxd5Dnht8yS z&;-d;W!rHy`3n`W|@7`YA&)N9WRPm_! z(JZ~0q?H)E*zaoHWybibq=$(& zN@OrRTbSo@LUJA^KH^SsXN1U%oGUB8ATZ4@iB0SpS z*5Hpj)!+eR^SNM1;iVG}Mqq*LJ+`!70jYc!p1&6a{BS+NG~*|R>;~Ijr=TfxC7~t4eYH!OG1HX<`hy8D zZ)Qxch}1=5srQy|53#LqXd`Cj8<%^CAgdQFjugw{#~mYVMf;T*fUH^MXp=FDJVf-{ zC0VU9`66^n;7F*!w4?Gf5Yf_AiQliuH8tuD%#XS&DhM z?jLE-uLjsE`HCTO$NrFrvs6fLZcodNllOEg#wW0Ob>k&T)1q5Cn;n&8_59Vl-bYTYADuQOt6%7x`EK7eZBy6NM<9jGZ7j IY_IZv0OxeW_5c6? diff --git a/flutter_cache_manager/test/images/image-120.bmp b/flutter_cache_manager/test/images/image-120.bmp deleted file mode 100644 index 1b93207ef76c63d6b0c6657b1b7e7d8b0cad8bd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43254 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 diff --git a/flutter_cache_manager/test/test_images.dart b/flutter_cache_manager/test/test_images.dart new file mode 100644 index 00000000..9dd1cfa5 --- /dev/null +++ b/flutter_cache_manager/test/test_images.dart @@ -0,0 +1,138 @@ +/// Images used by the tests, embedded as base64 so they can be decoded +/// without a file system. +/// +/// `flutter test --platform chrome` cannot read files: `dart:io` is not +/// available there. Keeping the bytes in the test bundle lets the same tests +/// run on the VM and in the browser, so the resize behaviour on web (where +/// `ImageDescriptor.width` and `height` are not supported) is covered too. +/// +/// All of them are 120x120, the minimal size the resize tests need, and are +/// kept as small as possible to stay readable. They were made with Pillow: +/// +/// ```python +/// img = Image.new('RGB', (120, 120), (32, 96, 192)) +/// img.quantize(colors=8).save('image-120.png', optimize=True) +/// ``` +library; + +/// Static 120x120 png. +const imagePng120 = + 'iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4AgMAAABECt5BAAAACVBMVEXwyB4gYMDIKCiUsMyeAA' + 'ABLElEQVR42u2WMY6DMBBFvwlIKV1kqbfMMXwEit2jpcwNUnAEH4UDpKAHydtkE+aPjWIJ7VKY' + 'Ann8mD/jGRswkNeHE2aF1avgggsueH+41lNNh3BLejcdYL5S2HSvewQ//BoXxc3v4AQAOBC+Po' + 'O0g/ZemDYifqYwJH5ZjNuBvYVllfiZF3FIagPtIL0pT0szR9WunH5/UncotQv5tkO1tnNsTuyj' + '3mlVOjPAoP5efF7XxZ2i7v3UjKZ2szMWOU/1ZuJWU/NXLw9XXpp7w/4fY4+ahs3EZ03nzcSDpm' + 'OGuC66z/BWZQsCq4XPOR1TKxulN6fuJR45s6zdMnPiEgfOjMQ9WxLfl8ak+h14HRWS6l7ju9Rm' + 'HKhGXJb++aCP4UmGUUV9/GtNPo5D/7oDP+j1N9kWT81DAAAAAElFTkSuQmCC'; + +/// Static 120x120 webp. +const imageWebp120 = + 'UklGRkwCAABXRUJQVlA4IEACAACQEQCdASp4AHgAPrVUpk0nJKOiJhqqAOAWiU3cLiYSKsP51q' + '/3K/yI5wvQJc751vEv+O+6D3Af1X2AeJL/ouoB5hv2f/Yz3gPRf6AH9V/wHWM+hf5ZPsc/tv6U' + 'GaAALATZ5zdWBUWGhxz4FTEPHNR0FuBf+9ssCqNsqCPqki1UQ64OdJja38kSq5/xgZ5tuoLo8J' + 'yBwAD+/J6P50uqCJS7mhfSQwrrk/5whJQucgetgOr9CbPgqPoP5Mtthf7Zsg3LCZJJVeRgMYar' + 'xMaOl6ibLZ13kJWCCHd0SFjAZOv6vcMEAlCrdGnpiGMKMuc6xpxp5y0JeDddJwx8v4hbFiSiHa' + 'O+g5jKsu3/Uza13TTikWaTPxxHXB9GzQogAx0VyHkTTSH6SHz5Q9U8n3b9ceAvd2MqPOWASocs' + 'AcPJXUH0L37dNrBrpXkHelAHkNtrhp6odEkbgDO5ygFKJ7hLEITJ+f5khCBWLCLE0VM9yhb/sC' + 'p/po7UFmju89OX0T2nymo6io3i+K0FXloS8G2ALP8EpHOHpVZ/+fg7ZuNW/+DjDOVAdBJ8SncC' + 'ShE+X3ZjAhAj2toEDwxfRmCvqeAYyHvqf3TNWXrIqbTgyobZLU4zhFs6DCxRH1L8zKcev/ducr' + 'ijGfLQPbmd0myX/QL0s2bQrlv/iKwtbK6CZ2zLzovSHJFy83eD4mb+gBHGhWs4kKnxit4YGnaR' + 'VTcx/fGt4PFTsKybpx+1iJjLZ8qEfDV8LwiWchM9zC8nok5K60dkoAA='; + +/// Static 120x120 bmp. +const imageBmp120 = + 'Qk2+BwAAAAAAAD4AAAAoAAAAeAAAAHgAAAABAAEAAAAAAIAHAADEDgAAxA4AAAIAAAACAAAAAA' + 'AAAP///wCSSSSUklJJSSySkkkkooQAJJJJISSEkpKBJSSSSRQpAEkkkkpJKSUkqkhKVJSlUgCS' + 'SSSUklJQSSSSkoEikIQAJJZJISSEipJJJSSqVCUpAElAkkpJKSkkkkhJJIVIkgCSVSSUkpKSSS' + 'SSkkkolSQAJJJJISREpJJJKSSSRSJJAEkkkkpJqQkkkkRJJVCUkgCSSSSklBJSSSSqkkgtSSQA' + 'JJJJESKkpJJJCSSVQJJJAEkkkmpUSQkkklJJJJUkkgCSSSUEgpJSySykkkkqSSQAJJJIqVUkiB' + 'KBCSSSQJVJAEkqlRJISVKkqlJJJJUgkgCSQSSkkpIkSSSkqkkiVSQAJKpJKSUlSpJJCQSSVJJZ' + 'AEkkkkJISSkkklKpJIkkggCSSSSUkpJCSSUkkklSSSoAJJJJKSkklJJIiSSSJJJUAEkkkkJESS' + 'kklVJJKUkkgQCSSSSUqpJSSSEkkkpSSSoAJJJJKQkkhJJKSSSQhJJUAEkkklJSSSkklJJJJSkk' + 'gQCSSSUEpJJSSSEkkkpSSSoAJJJIqQkkhKJKSSSQhJJEAEkqkpJSSSlUlJJJJSkkqQCSQSUkpJ' + 'JECSEkkkpEkpIAJKpISQkkqVJKSSSQqSQkAEkkkpJSyRIklJLJJRKpSQCSSSUkiBKlSSEkEkik' + 'EpIAJJJISVKklJKWSqSVSqQkAEkkkpIkSSEkQJCSUhJJSQCSSSUpSpJKSrUlJISklJIAJJSQpS' + 'kklJECSlKpSSIkAEklVQhCSSEqVJCUEhJVSQCSSEJSlJJKQUlFIqSkhJIAJJKUpEkkpKoSKkpJ' + 'SSkkAEklKQqSSSkkpVCUkhJSSgCSSEJRJJJKSRCFISqkhJEAJJKUikkkkJKlKkpISSkqAEkkUl' + 'SSSSUkSJCUkpJSQQCSioUhJJJKSpUlISUlBKoAJFEpSkkkkJJSSkpISKkkAElKRCqVSSUkhJCk' + 'kpUSSQCSkqlAoJJSSSklESUkpJIAJCQlVRUkiJJEkqpKSUkkAElJSBJSSVUkqUgkkJKSSQCSkp' + 'akpJISSRIlSSUkJJIAJCQgSQkkpJKlVJJKSUkkAElJSpJSSUkklCEkkJKSSQCSkpEkpJISSSFK' + 'SSUkJJIAJCQqSQkkpJJKlJJKSUkkAElJRJJSUUkklCEokJSSSQCSJSkkiUoSSSFKRSUiJVIAJU' + 'hSSVIUqSJalJSSVUgkAEiShJIkoUSUgSEpSoSVSQBVKSlVSkoVSSpKQlApJJIAAEREABEUqFJU' + 'lJSVUkkkAP+7uv/u6UKEgSkpJISSSQCq7u+qu7oVKSpCQkkpJJIA/7u9f+7uqFJElJSSUkkkAK' + 'ru1+q7ukKEqSlJJISSSQD/u/6/7u6VKRJCIkkpJJIAqu6r6ru7KESklVSSSklJAP+7/r/u78Wp' + 'SShJJKESJACq7qvqu7rIEhJCkkkWpUkA/7v+3+7vsqSklSSUoEkqAKruq7q7uuRJEkhJIkqSQQ' + 'D/u/737u+6lKSSklURJKoAqu7V3Vu26kJKSSSEqkkkAP+7f7f++76UkSRJKSSSSQCq77b+q67r' + 'qyqSklJJJJIA/7r7q/77fqBCSSSEkkkkAKrvrv6rr6vqlKRJKSSSSQD/uvur/vr+uSkqklJJJJ' + 'IAqu+u/quvq+pCQkkEkkkkAP+2+6v++v65FJSkqSSSSQCq+69+q6+r6qkpKVJJJJIA/6766/76' + '/rhCQkoEkkkkAKr7b96rr6vqlJSSqSWSSQD/r7t1/vr+uSkpJJJIJJIAqvrt36uvq+pCQkkkpU' + 'kkAP+vvvr++365FJSSSRCSSQCq+uuvq63r6mlJJJJVJJIA/6+++37/vtkCIkkpQlUkAKr6663r' + 'qut6VVSSShSCVQD/r77/vv+/6ohJJJKpVIIAqvrrquuq6rhSkkkkkklUAP+vvv++/7/qpSSSSS' + 'SSSQCq+uuq66rquQhJSJJJJJIA/6++/77/v+pSkiUkkkkkAKr666rrquq5JSVSSSSSSQD/t79/' + 'v3+/6khIJJJJJJIAqt7q6urq6rkqlUkkkkkkAP/7v9+/37/qQSSSSSSSSQCqru167XrtWSpJJJ' + 'JJJJIA//u717vXu/pEkkkkkkkkAKqu737vfu9pKSSSSSSSSQD/+73bvdu92pJJJJJJJJIAqq7X' + 'btdu13hUkkkkkkkkAP/7/fv9+/3qgSVSqVSqSQCqrVatVq1WuSpIJBIJBJYA/////////+pElU' + 'qlUqlAAKqqqqqqqqq0qSSSSSSSVQD/////////+RJJJJJJJJIAqqqqqqqqqqqkkkkkkkkkAP//' + '///////4SSSSSSSSSQCqqqqqqqqqqpJJJJJJJJIA//////////kkkkkkkkkkAKqqqqqqqqqqUS' + 'SSSSSSSQD/////////+QpJJJJJJJIAqqqqqqqqqqpUkkkkkkkkAP/////////5ISSSSSSSSQCq' + 'qqqqqqqqqkpJJJJJJJIA//////////kkkkkkkkkkAKqqqqqqqqqqSSSSSSSSSQD/////////+S' + 'SSSSSSSSQAqqqqqqqqqqpJJJJJJJJJAP/////////5JJJJJJJJJAA='; + +/// Animated (3 frames) 120x120 gif. +const animatedGif120 = + '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'; + +/// Animated (3 frames) 120x120 webp. +const animatedWebp120 = + 'UklGRsACAABXRUJQVlA4WAoAAAASAAAAdwAAdwAAQU5JTQYAAAAAAAAAAABBTk1GngAAAAAAAA' + 'AAAHcAAHcAAGQAAAJWUDgghgAAADAIAJ0BKngAeAA+0WStUaglJCKknjgBABoJZ27dX3DAABu+' + 'iAfwGtaZ9ow/6ZF5Iumvx5lQGRlGupsIxacZ2/KzBCi0yBCl3ZgAAP7zB/EqJeD9BD//N5uQfy' + 'UzKbkH27f+vb+By5BynUViE1OcSDwTUi2oi3VHfCEwysjzhCYZWR5whAAAQU5NRuoAAAAAAAAA' + 'AAB3AAB3AABkAAAAQUxQSBYAAAABDzD/ERFCTEPoX1oCpy2i/xNgOpwIVlA4ILQAAAC0CQCdAS' + 'p4AHgAPtFiqk8CiIAAAaCWVu3V9bJn+IGz4cz/CBLAADnRE39Ug42uYKZLMS8Ri+tzVIopfv5l' + '/v2p1JynJZM+zaHpTzvmX+/bGm0Zll2MAAD+3hozTB+Z4Lt/gXHce/tFTcvMFZfsxNPrQESvY+' + 'aw+mSv+2OB6uNl/a0ZtOJY+besf+WgR/l67hT9e3ns2d6U4dM1JoigbeEVEjMCCfF56uEOzIAI' + 'wcgAjhyAAABBTk1G/AAAAAAAAAAAAHcAAHcAAGQAAABBTFBIFgAAAAEPMP8REUJMQ+hfWgKnLa' + 'L/E2A+XAhWUDggxgAAAPQIAJ0BKngAeAA+0WKmTwKIlYABoJY27dXpzawCJqvkzIfQGNaRvTBb' + 'SX3uFhmq+sJIY+oFjax57YzSxIwT2xmhOT2kLnIVkYJ7YzSxIwHAAP7tCH//zNH75PsN5Af+J/' + 'D4djBSt/0IMH/t/qif6mdXfa0f1M74VG3IL/NWrelE4S4m+dTGVYWq+dUrY9Mh5VyiMuw3TD4g' + 'rStK/suynznFY9+IIR1h27tiyviT1KWzOMs0khTL+rzSBUKzAgTvyksmlCQAAA=='; + +/// Animated (3 frames) 120x120 png. +const animatedApng120 = + 'iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4AQMAAAADqqSRAAAABlBMVEUAgP8KCgrdKYvuAAAACG' + 'FjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAB4AAAAeAAAAAAAAAAAAAEACgAAjNzB+AAA' + 'ACVJREFUeNpjYGBgYGBgkP8PBUwMqGCUP8of5Y/yR/mj/FE+9fkA17QJBpLlpncAAAAaZmNUTA' + 'AAAAEAAAB4AAAAeAAAAAAAAAAAAAEACgAAF68rLAAAAClmZEFUAAAAAnja7cchEgAgDMCwHnyc' + 'n3caP9m4AFxVfQCHX++99973P/4eB2zCVlVhAAAAGmZjVEwAAAADAAAAeAAAAHgAAAAAAAAAAA' + 'ABAAoAAPo5+MUAAAArZmRBVAAAAAR42mNggAD+////////j4GJARWM8kf5o/xR/ih/lD/Kpz4f' + 'AH2rBvcc4MJOAAAAAElFTkSuQmCC';