Skip to content

Specifying cacheWidth / cacheHeight can degrade the image quality #56239

Closed

Description

@nioncode

@iskakaushik asked me to post this as a separate issue from #48885.

When using the cacheWidth and cacheHeight parameters, the resulting image has less details than without using the parameters, although they are set to the same values as the image's width and height.

I was under the assumption that if I supply the same values to the cacheWidth / cacheHeight parameters as I do to the width / height parameters, then the quality should be exactly the same, but the cache uses the given dimensions instead of a possibly much larger native size. I thought the cache parameters are basically just a way to tell the engine that I will never draw the image in a higher resolution, so it is ok to cache it at the specified resolution in order to reduce memory footprint.

However, as the pictures below show, when using the cache parameters, the image gets drawn in less quality, which is especially visible on the edges (e.g. compare the e or the a).

Comparison pictures:

Without cache:
cache_no

With cache:
cache_yes

I stumbled upon this with a real picture in my app (that I can't share) and was wondering why it renders with these artifacts and found by chance that the artifacts are gone when I don't use the cache parameters. I originally noticed this on a Pixel 2, but the screenshots were taken from Android Emulator, where it is also clearly visible.

Sample code:

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(appBar: AppBar(title: Text('Test')),
        backgroundColor: Colors.black,
        body: Image.asset(
          'assets/test.png',
          width: 200,
          height: 200,
          cacheWidth: 200,
          cacheHeight: 200,
        ),
      ),
    );
  }
}

Source Image:

Logs
[  +40 ms] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git -c
log.showSignature=false log -n 1 --pretty=format:%H
[  +58 ms] Exit code 0 from: git -c log.showSignature=false log -n 1 --pretty=format:%H
[        ] 0b8abb4724aa590dd0f429683339b1e045a1594d
[        ] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git describe --match
v*.*.* --first-parent --long --tags
[   +8 ms] Exit code 0 from: git describe --match v*.*.* --first-parent --long --tags
[        ] v1.12.13+hotfix.8-0-g0b8abb472
[   +5 ms] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git rev-parse
--abbrev-ref HEAD
[   +7 ms] Exit code 0 from: git rev-parse --abbrev-ref HEAD
[        ] stable
[  +36 ms] Artifact Instance of 'AndroidMavenArtifacts' is not required, skipping update.
[   +3 ms] Artifact Instance of 'AndroidInternalBuildArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'IOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterWebSdk' is not required, skipping update.
[   +2 ms] Artifact Instance of 'WindowsEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'LinuxEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'LinuxFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerDebugSymbols' is not required, skipping update.
[  +26 ms] executing: /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped/jre/bin/java -version
[ +110 ms] Exit code 0 from: /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped/jre/bin/java
-version
[   +2 ms] openjdk version "1.8.0_212-release"
           OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
           OpenJDK 64-Bit Server VM (build 25.212-b4-5784211, mixed mode)
[  +11 ms] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git rev-parse
--abbrev-ref --symbolic @{u}
[   +7 ms] Exit code 0 from: git rev-parse --abbrev-ref --symbolic @{u}
[        ] origin/stable
[        ] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git ls-remote
--get-url origin
[   +7 ms] Exit code 0 from: git ls-remote --get-url origin
[        ] https://github.com/flutter/flutter.git
[  +19 ms] executing: [/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped/] git -c
log.showSignature=false log -n 1 --pretty=format:%ar
[   +8 ms] Exit code 0 from: git -c log.showSignature=false log -n 1 --pretty=format:%ar
[        ] 3 months ago
[  +14 ms] executing: /home/nion/.cache/flutter/artifacts/engine/android-arm-profile/linux-x64/gen_snapshot
[  +16 ms] Exit code 255 from: /home/nion/.cache/flutter/artifacts/engine/android-arm-profile/linux-x64/gen_snapshot
[        ] At least one input is required
           Usage: gen_snapshot [<vm-flags>] [<options>] <dart-kernel-file>             

           Common options:                                                             
           --help                                                                      
             Display this message (add --verbose for information about all VM options).
           --version                                                                   
             Print the VM version.                                                     

           To create a core snapshot:                                                  
           --snapshot_kind=core                                                        
           --vm_snapshot_data=<output-file>                                            
           --isolate_snapshot_data=<output-file>                                       
           <dart-kernel-file>                                                          

           To create an AOT application snapshot as assembly suitable for compilation  
           as a static or dynamic library:                                             
           --snapshot_kind=app-aot-assembly                                            
           --assembly=<output-file>                                                    
           [--obfuscate]                                                               
           [--save-obfuscation-map=<map-filename>]                                     
           <dart-kernel-file>                                                          

           To create an AOT application snapshot as an ELF shared library:             
           --snapshot_kind=app-aot-elf                                                 
           --elf=<output-file>                                                         
           [--strip]                                                                   
           [--obfuscate]                                                               
           [--save-obfuscation-map=<map-filename>]                                     
           <dart-kernel-file>                                                          

           AOT snapshots can be obfuscated: that is all identifiers will be renamed    
           during compilation. This mode is enabled with --obfuscate flag. Mapping     
           between original and obfuscated names can be serialized as a JSON array     
           using --save-obfuscation-map=<filename> option. See dartbug.com/30524       
           for implementation details and limitations of the obfuscation pass.
[  +11 ms] executing: /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped/jre/bin/java -version
[  +45 ms] Exit code 0 from: /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped/jre/bin/java
-version
[        ] openjdk version "1.8.0_212-release"
           OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
           OpenJDK 64-Bit Server VM (build 25.212-b4-5784211, mixed mode)
[   +2 ms] java -version
⣽[ +124 ms] executing: /home/nion/opt/android-sdk/platform-tools/adb devices -l
[   +7 ms] Exit code 0 from: /home/nion/opt/android-sdk/platform-tools/adb devices -l
[        ] List of devices attached
           192.168.1.31:5555      device product:walleye model:Pixel_2 device:walleye transport_id:17
           emulator-5554          device product:sdk_phone_x86 model:Android_SDK_built_for_x86 device:generic_x86 transport_id:20 [   +6 ms] [✓] Flutter (Channel stable, v1.12.13+hotfix.8, on Linux, locale en_US.UTF-8)
[   +1 ms]     • Flutter version 1.12.13+hotfix.8 at
/nix/store/bxyz9r12vvlg2n37kmpk2rqjxi4s70gj-flutter-stable-1.12.13+hotfix.8-unwrapped
[   +1 ms]     • Framework revision 0b8abb4724 (3 months ago), 2020-02-11 11:44:36 -0800
[        ]     • Engine revision e1e6ced81d
[        ]     • Dart version 2.7.0
⣽[  +11 ms] /home/nion/opt/android-sdk/platform-tools/adb -s 192.168.1.31:5555 shell getprop
[  +65 ms] executing: /home/nion/opt/android-sdk/tools/bin/sdkmanager --licenses                                                  ⢿[ +211 ms] ro.hardware = walleye
[        ] ro.build.characteristics = nosdcard
[   +1 ms] /home/nion/opt/android-sdk/platform-tools/adb -s emulator-5554 shell getprop                                           ⡿[  +83 ms] ro.hardware = ranchu                                                                                                   ⣯
[+3572 ms] [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[        ]     • Android SDK at /home/nion/opt/android-sdk
[        ]     • Android NDK location not configured (optional; useful for native profiling support)
[        ]     • Platform android-29, build-tools 29.0.3
[        ]     • ANDROID_HOME = /home/nion/opt/android-sdk
[        ]     • Java binary at: /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped/jre/bin/java
[        ]     • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
[        ]     • All Android licenses accepted.
[        ] [✓] Android Studio (version 3.6)
[        ]     • Android Studio at /nix/store/mgb6pwg4r24l2y7qxfz9l4bfmn4p335i-android-studio-stable-3.6.3.0-unwrapped
[        ]     • Flutter plugin version 45.0.1
[        ]     • Dart plugin version 192.7761
[        ]     • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
[        ] [✓] Connected device (2 available)
[        ]     • Pixel 2                   • 192.168.1.31:5555 • android-arm64 • Android 10 (API 29)
[        ]     • Android SDK built for x86 • emulator-5554     • android-x86   • Android 10 (API 29) (emulator)
[        ] • No issues found!
[   +7 ms] "flutter doctor" took 4,508ms.

Activity

  1. changed the title [-]`cacheWidth` / `cacheHeight` degrading image quality[/-] [+]cacheWidth / cacheHeight degrade image quality[/+] on May 3, 2020
  2. added
    a: imagesLoading, displaying, rendering images
    engineflutter/engine related. See also e: labels.
    on May 3, 2020
  3. changed the title [-]cacheWidth / cacheHeight degrade image quality[/-] [+]Specifying cacheWidth / cacheHeight can degrade the image quality[/+] on May 3, 2020
  4. davidmartos96 commented on May 8, 2020

    @davidmartos96
    Contributor

    I'd like to share a sample with a more detailed image as I think is much more noticeable than the provided one.

    Source image from Unsplash

    I have also tried multiplying the cache dimension by the devicePixelRatio and it looks much better, but I don't know if that is really the issue.
    Here is the comparison (Flutter 1.17.0)

    Without devicePixelRatio

      @override
      Widget build(BuildContext context) {
        final url =
            "https://images.unsplash.com/photo-1550613097-fe6c2c321cd3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1351&q=80";
        return Scaffold(
          appBar: AppBar(),
          body: SingleChildScrollView(
              child: Column(
            children: <Widget>[
              Image.network(
                url,
                width: 300,
                fit: BoxFit.cover,
              ),
              Image.network(
                url,
                width: 300,
                fit: BoxFit.cover,
                cacheWidth: 300,
              )
            ],
          )),
        );
      }

    screenshot

    With devicePixelRatio

      @override
      Widget build(BuildContext context) {
        final url =
            "https://images.unsplash.com/photo-1550613097-fe6c2c321cd3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1351&q=80";
        
        final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
        return Scaffold(
          appBar: AppBar(),
          body: SingleChildScrollView(
              child: Column(
            children: <Widget>[
              Image.network(
                url,
                width: 300,
                fit: BoxFit.cover,
              ),
              Image.network(
                url,
                width: 300,
                fit: BoxFit.cover,
                cacheWidth: (300 * devicePixelRatio).round(),
              )
            ],
          )),
        );
      }

    screenshot2

  5. pedromassangocode commented on Dec 8, 2020

    @pedromassangocode

    Reproducible on latest stable channel.

    Screen Shot 2020-12-08 at 12 14 03

    Code
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final url =
            "https://images.unsplash.com/photo-1550613097-fe6c2c321cd3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1351&q=80";
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    Image.network(
                      url,
                      width: 300,
                      fit: BoxFit.cover,
                    ),
                    Image.network(
                      url,
                      width: 300,
                      fit: BoxFit.cover,
                      cacheWidth: 300,
                    )
                  ],
                )),
          ),
        );
      }
    }
    flutter doctor -v
    [✓] Flutter (Channel stable, 1.22.4, on Mac OS X 10.15.7 19H2 darwin-x64, locale en)
        • Flutter version 1.22.4 at /Users/pedromassango/dev/SDKs/flutter_stable
        • Framework revision 1aafb3a8b9 (4 weeks ago), 2020-11-13 09:59:28 -0800
        • Engine revision 2c956a31c0
        • Dart version 2.10.4
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
        • Android SDK at /Users/pedromassango/Library/Android/sdk
        • Platform android-30, build-tools 30.0.2
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 12.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 12.1, Build version 12A7403
        • CocoaPods version 1.9.3
    
    [!] Android Studio (version 4.1)
        • Android Studio at /Applications/Android Studio.app/Contents
        ✗ Flutter plugin not installed; this adds Flutter specific functionality.
        ✗ Dart plugin not installed; this adds Dart specific functionality.
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    
    [✓] IntelliJ IDEA Community Edition (version 2020.2.4)
        • IntelliJ at /Applications/IntelliJ IDEA CE.app
        • Flutter plugin installed
        • Dart plugin version 202.8070
    
    [✓] VS Code (version 1.51.1)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.16.0
    
    [✓] Connected device (1 available)
        • sdk gphone x86 arm (mobile) • emulator-5554 • android-x86 • Android 11 (API 30) (emulator)
    
    ! Doctor found issues in 1 category.
    Process finished with exit code 0
    
  6. added
    P2Important issues not at the top of the work list
    on Dec 10, 2020
  7. lucascardo12 commented on May 13, 2022

    @lucascardo12

    I have the same problem on some phones the quality is perfect but on some phones it is pixelated

  8. maRci002 commented on Feb 9, 2023

    @maRci002
    Contributor

    Flutter's (3.7.2) current documentation for Image:

    The Image.asset, Image.network, Image.file, and Image.memory constructors allow a custom decode size to be specified through cacheWidth and cacheHeight parameters. The engine will decode the image to the specified size, which is primarily intended to reduce the memory usage of ImageCache.

    Flutter documentation should be updated since Image.width / Image.height uses logical pixels while Image.cacheWidth / Image.cacheHeight uses physical pixels.

    See @davidmartos96 #56239 (comment) example.

  9. 8 remaining items

  10. maRci002 commented on Jun 28, 2023

    @maRci002
    Contributor
    final widgetWidth = (constraint.maxWidth * devicePixelRatio).round();

    This solution can be dangerous because if the widget changes its size, for instance, if the user resizes the window, you would ask the image provider to decode the image every frame and cache it.

    I think the best solution is generate multiple resolutions of the image, then you choose the best image:

    abstract class SizeInterface {
      Size get size;
    }
    
    class AppImage implements SizeInterface {
      final String url;
      final int width;
      final int height;
    
      @override
      Size get size => Size(width.toDouble(), height.toDouble());
    }
    
    static T? findClosestByAxis<T extends SizeInterface>({
      required List<T> items,
      required Axis axis,
      required double targetSize,
    }) {
      T? bestItem;
      var bestDelta = double.infinity;
    
      for (final item in items) {
        final size = axis == Axis.horizontal ? item.size.width : item.size.height;
        final delta = (size - targetSize).abs();
    
        if (delta < bestDelta) {
          bestItem = item;
          bestDelta = delta;
        }
      }
    
      return bestItem;
    }

    Then call it

    final widgetWidth = (constraint.maxWidth * devicePixelRatio).round();
    
    final landscapeImage = findClosestByAxis(
      items: landscapeImages,
      axis: Axis.horizontal,
      targetSize: widgetWidth,
    );
    
    // use `landscapeImage` without any memCacheWidth / memCacheHeight
  11. ranulfo commented on Jun 28, 2023

    @ranulfo
    landscapeImages

    this time it was not very clear to me. how can i set the initial values ​​for landscapeImages?, i did it like this:

    final landscapeImageW = findClosestByAxis(
            items: [AppImage(width: imageWidth, height: imageHeight)],
            axis: Axis.horizontal,
            targetSize: widgetWidth,
          );
    

    and it didn't work

  12. maRci002 commented on Jun 28, 2023

    @maRci002
    Contributor

    this time it was not very clear to me. how can i set the initial values ​​for landscapeImages?

    Example code

    YouTube API is very similar

    import 'dart:convert';
    
    import 'package:equatable/equatable.dart';
    import 'package:flutter/painting.dart';
    
    void main() {
      const myCar = CarModel(
        name: 'Hyundai',
        image: ImageGroupModel(
          images: [
            ImageModel(url: 'https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=568', width: 652, height: 936),
            ImageModel(url: 'https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480', width: 480, height: 791),
            ImageModel(url: 'https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320', width: 320, height: 527),
          ],
        ),
      );
    
      final myCarJson = json.encode(myCar.toMap());
      print(myCarJson);
    
      final myCarFromJson = CarModel.fromMap(json.decode(myCarJson) as Map<String, dynamic>);
      print('Equals: ${myCar == myCarFromJson}');
    
      const screenWidthList = <double>[100, 360, 500, 500, 1080];
    
      for (final screenWidth in screenWidthList) {
        final bestImage = SizeProximityUtil.findClosestByAxis(items: myCarFromJson.image.images, axis: Axis.horizontal, targetSize: screenWidth);
        print('screenWidth: $screenWidth, bestImage: $bestImage');
      }
    }
    
    abstract class SizeInterface {
      Size get size;
    }
    
    class SizeProximityUtil {
      const SizeProximityUtil._();
    
      static T? findClosestByAxis<T extends SizeInterface>({
        required List<T> items,
        required Axis axis,
        required double targetSize,
      }) {
        T? bestItem;
        var bestDelta = double.infinity;
    
        for (final item in items) {
          final size = axis == Axis.horizontal ? item.size.width : item.size.height;
          final delta = (size - targetSize).abs();
    
          if (delta < bestDelta) {
            bestItem = item;
            bestDelta = delta;
          }
        }
    
        return bestItem;
      }
    }
    
    class CarModel extends Equatable {
      const CarModel({
        required this.name,
        required this.image,
      });
    
      factory CarModel.fromMap(Map<String, dynamic> map) {
        final image = map['image'] as Map<String, dynamic>;
    
        return CarModel(
          name: map['name'] as String,
          image: ImageGroupModel.fromMap(image),
        );
      }
    
      final String name;
      final ImageGroupModel image;
    
      Map<String, dynamic> toMap() {
        return {
          'name': name,
          'image': image.toMap(),
        };
      }
    
      @override
      List<Object> get props => [name, image];
    }
    
    class ImageGroupModel extends Equatable {
      const ImageGroupModel({required this.images});
    
      factory ImageGroupModel.fromMap(Map<String, dynamic> map) {
        final images = map['images'] as List<dynamic>;
    
        return ImageGroupModel(
          images: images.map((e) => ImageModel.fromMap(e as Map<String, dynamic>)).toList(),
        );
      }
    
      final List<ImageModel> images;
    
      Map<String, dynamic> toMap() {
        return {
          'images': images.map((e) => e.toMap()).toList(),
        };
      }
    
      @override
      List<Object> get props => [images];
    }
    
    class ImageModel extends Equatable implements SizeInterface {
      const ImageModel({
        required this.url,
        required this.width,
        required this.height,
      });
    
      factory ImageModel.fromMap(Map<String, dynamic> map) {
        return ImageModel(
          url: map['url'] as String,
          width: map['width'] as int,
          height: map['height'] as int,
        );
      }
    
      final String url;
      final int width;
      final int height;
    
      @override
      Size get size => Size(width.toDouble(), height.toDouble());
    
      @override
      List<Object> get props => [url, width, height];
    
      Map<String, dynamic> toMap() {
        return {
          'url': url,
          'width': width,
          'height': height,
        };
      }
    }
    

    Output:

    {"name":"Hyundai","image":{"images":[{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=568","width":652,"height":936},{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480","width":480,"height":791},{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320","width":320,"height":527}]}}
    Equals: true
    screenWidth: 100, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320, 320, 527)
    screenWidth: 360, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320, 320, 527)
    screenWidth: 500, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480, 480, 791)
    screenWidth: 500, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480, 480, 791)
    screenWidth: 1080, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=568, 652, 936)
    
  13. ranulfo commented on Jun 28, 2023

    @ranulfo

    this time it was not very clear to me. how can i set the initial values ​​for landscapeImages?

    Example code
    Output:

    {"name":"Hyundai","image":{"images":[{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=568","width":652,"height":936},{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480","width":480,"height":791},{"url":"https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320","width":320,"height":527}]}}
    Equals: true
    screenWidth: 100, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320, 320, 527)
    screenWidth: 360, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=320, 320, 527)
    screenWidth: 500, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480, 480, 791)
    screenWidth: 500, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=480, 480, 791)
    screenWidth: 1080, bestImage: ImageModel(https://images.unsplash.com/photo-1662010021854-e67c538ea7a9?w=568, 652, 936)
    

    thank you for everything. this solution will be more work because I will have to resize and record new images on the server side. while the distortion generated that i mentioned in the previous comment, do you have any suggestion how to solve it?

  14. maRci002 commented on Jun 29, 2023

    @maRci002
    Contributor

    the distortion generated that i mentioned in the previous comment, do you have any suggestion how to solve it?

    Maybe by finding the closest image and not resizing it (for example, not passing memCacheWidth), you can solve your issue. Perhaps passing imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet to CachedNetworkImage will also solve the problem.

    distortion maybe caused by #122541

  15. Xim-ya commented on Nov 14, 2023

    @Xim-ya

    I have referenced this issue and organized related content for a blog post titled "Save Your Memory Usage By Optimizing Network Image in Flutter."

    The post includes concepts of resizing images and methods for dynamically setting cache values based on the image layout.

    For anyone who is interested, please take a look.

  16. ebwood commented on Jan 8, 2024

    @ebwood

    @Xim-ya Does any solution to solve the problem like issue #52802?
    Says the original image size is 1000*1500, image widget size is 100 * 100,

    Image.network(
        'imageUrl',
        width: 100,
        height: 100,
    	fit: BoxFit.cover,
        cacheWidth: 100 * MediaQuery.of(context).devicePixelRatio.round(),
    );
    

    the resize image size will be 100 * 150, debugInvertOversizedImages will complain about oversize.
    add `cacheWidth: 100 * MediaQuery.of(context).devicePixelRatio.round()' will distort image.

    Is there any solution to crop image from 100*150 to 100 * 100 in memory to avoid the oversize complain?

  17. AndreiMisiukevich commented on May 13, 2025

    @AndreiMisiukevich

    devicePixelRatio works incorrectly on mac retina
    it returns 2, and image looks degraded

  18. gaganyadav80 commented on Aug 20, 2025

    @gaganyadav80
    Contributor

    If anyone facing issue where providing both cacheHeight and cacheWidth does not respect boxFit, then you can fix it using the below code:

    Image(
      image: ResizeImage(
        imageProvider,
        height: context.cacheSize(height),
        width: context.cacheSize(width),
        policy: ResizeImagePolicy.fit, // <-- use this
      ),
      fit: BoxFit.cover,
    );
  19. github-actions commented on Sep 2, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  20. locked as resolved and limited conversation to collaborators on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: imagesLoading, displaying, rendering imagesa: qualityA truly polished experienceengineflutter/engine related. See also e: labels.found in release: 3.7Found to occur in 3.7found in release: 3.8Found to occur in 3.8has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions