Skip to content

Commit

Permalink
Support decoding ISO HDR (avif/heic/jpeg-xl, etc) (SDWebImage#3778)
Browse files Browse the repository at this point in the history
* Support HDR

* Support HDR

* When using SDWebImageProgressiveLoad and setting it to SDWebImageContextImageDecodeToHDR, it will be decoded to HDR only when loading is complete.

* When SDImageIOAnimatedCoder turns on incremental, it does not need to be decoded to HDR when it is not completed

* Sample image to remove sensitive information

* 1、use remote resources for HDR testing
2、add simulator log for HDR

* Support HDR encode

* Support HDR encode

* imageRef release error

* HDR Decoded is not required for decoding, otherwise the type will be lost

* HDR encode format

* hdrImageRef  not released correctly

* HDR image must be lazy decode

* HDR image must be lazy decode

* JEPG HDR image must be lazy decode, otherwise it will crash

* HDR, encoding properties add kCGImageDestinationEncodeToISOGainmap, compatible with SDR displays while preserving HDR

* 支持 decode to HDR

* HDR encoding is not currently supported

* add UIImage.sd_isHighDynamicRange, use check UIImage is HDR

* refactor: Do not hack on HEICS and distinguish static/aniamted image encoding UTI

* refactor: Move cross-platform screen info into SDDeviceHelper

* change: Do not disable force decode when turn on decodeToHDR

Need actually check the HDR info of CGImage, or better, we can pre-decode to drop lazy HDR image

* change: use UIImage.imageRendererFormat when force decode using graphics renderer

This can inherit the possible info like dynamic range

* fix: When decode HDR image to SDR, need specify the decode request

Tested on macOS 14.5 and iOS 18.0 behavior

* test: Added unit test for HDR decoding

* demo: Update the macOS demo to show the HDR image

* test: workaround the SDR decode on Simulator environment

* change: The `sd_isHighDynamicRange` should check CGImage as fallback as well

* test: temp disable a unused test case

---------

Co-authored-by: DreamPiggy <lizhuoli1126@126.com>
  • Loading branch information
CloudlessMoon and dreampiggy authored Feb 20, 2025
1 parent f3a1d91 commit f4f93c2
Show file tree
Hide file tree
Showing 29 changed files with 365 additions and 117 deletions.
16 changes: 13 additions & 3 deletions Examples/SDWebImage Demo/DetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,21 @@ - (void)configureView {
if (!self.imageView.sd_imageIndicator) {
self.imageView.sd_imageIndicator = SDWebImageProgressIndicator.defaultIndicator;
}
BOOL isHDR = [self.imageURL.absoluteString containsString:@"HDR"];
if (@available(iOS 17.0, *)) {
self.imageView.preferredImageDynamicRange = isHDR ? UIImageDynamicRangeHigh : UIImageDynamicRangeUnspecified;
}
SDWebImageContext *context = @{
SDWebImageContextImageDecodeToHDR: @(isHDR)
};
[self.imageView sd_setImageWithURL:self.imageURL
placeholderImage:nil
options:SDWebImageProgressiveLoad | SDWebImageScaleDownLargeImages
context:@{SDWebImageContextImageForceDecodePolicy: @(SDImageForceDecodePolicyNever)}
];
options:SDWebImageFromLoaderOnly | SDWebImageScaleDownLargeImages
context:context
progress:nil
completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
NSLog(@"isHighDynamicRange %@", @(image.sd_isHighDynamicRange));
}];
self.imageView.shouldCustomLoopCount = YES;
self.imageView.animationRepeatCount = 0;
}
Expand Down
3 changes: 3 additions & 0 deletions Examples/SDWebImage Demo/MasterViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibB
[SDWebImageDownloader sharedDownloader].config.executionOrder = SDWebImageDownloaderLIFOExecutionOrder;

self.objects = [NSMutableArray arrayWithObjects:
@"https://raw.githubusercontent.com/CloudlessMoon/SuperResources/master/Images/HEIC/TestHDR1.heic",
@"https://raw.githubusercontent.com/CloudlessMoon/SuperResources/master/Images/JPG/TestHDR1.JPG",
@"https://raw.githubusercontent.com/CloudlessMoon/SuperResources/master/Images/JPG/TestHDR2.JPG",
@"http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633", // requires HTTP auth, used to demo the NTLM auth
@"http://assets.sbnation.com/assets/2512203/dogflops.gif",
@"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif",
Expand Down
36 changes: 6 additions & 30 deletions Examples/SDWebImage OSX Demo/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
Expand Down Expand Up @@ -677,35 +677,15 @@
<rect key="frame" x="0.0" y="0.0" width="480" height="400"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nbD-Cx-g7b">
<rect key="frame" x="20" y="252" width="204" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="vAn-1d-apO"/>
</imageView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kv0-67-hkh">
<rect key="frame" x="256" y="252" width="204" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="f0P-c9-GMe"/>
</imageView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JIp-Or-vBM" customClass="SDAnimatedImageView">
<rect key="frame" x="20" y="116" width="204" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="NJq-m3-LlB"/>
</imageView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="khI-tY-l0M" customClass="SDAnimatedImageView">
<rect key="frame" x="256" y="116" width="204" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="WbV-Do-9qy"/>
</imageView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="NqE-Zi-qhY">
<rect key="frame" x="212" y="17" width="56" height="31"/>
<constraints>
<constraint firstAttribute="height" constant="26" id="WoQ-RY-bSV"/>
</constraints>
<rect key="frame" x="211" y="16" width="58" height="33"/>
<buttonCell key="cell" type="bevel" title="Clear" bezelStyle="regularSquare" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="OYN-fG-Plb">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="26" id="WoQ-RY-bSV"/>
</constraints>
</button>
</subviews>
<constraints>
Expand All @@ -715,10 +695,6 @@
</view>
<connections>
<outlet property="clearCacheButton" destination="NqE-Zi-qhY" id="eoz-cU-wWs"/>
<outlet property="imageView1" destination="nbD-Cx-g7b" id="t2R-8w-ybH"/>
<outlet property="imageView2" destination="kv0-67-hkh" id="i4k-5c-bno"/>
<outlet property="imageView3" destination="JIp-Or-vBM" id="Qcf-og-59T"/>
<outlet property="imageView4" destination="khI-tY-l0M" id="STy-c1-ihV"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
Expand Down
68 changes: 55 additions & 13 deletions Examples/SDWebImage OSX Demo/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@

@interface ViewController ()

@property (weak) IBOutlet NSImageView *imageView1;
@property (weak) IBOutlet NSImageView *imageView2;
@property (weak) IBOutlet SDAnimatedImageView *imageView3;
@property (weak) IBOutlet SDAnimatedImageView *imageView4;
@property (strong) NSImageView *imageView1;
@property (strong) SDAnimatedImageView *imageView2;

@property (strong) NSImageView *imageView3;
@property (strong) SDAnimatedImageView *imageView4;

@property (strong) NSImageView *imageView5;
@property (strong) NSImageView *imageView6;

@property (weak) IBOutlet NSButton *clearCacheButton;

@end
Expand All @@ -23,26 +28,39 @@ @implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.imageView1 = [NSImageView new];
self.imageView2 = [SDAnimatedImageView new];
self.imageView3 = [NSImageView new];
self.imageView4 = [SDAnimatedImageView new];
self.imageView5 = [NSImageView new];
self.imageView6 = [NSImageView new];

[self.view addSubview:self.imageView1];
[self.view addSubview:self.imageView2];
[self.view addSubview:self.imageView3];
[self.view addSubview:self.imageView4];
[self.view addSubview:self.imageView5];
[self.view addSubview:self.imageView6];

// For animated GIF rendering, set `animates` to YES or will only show the first frame
self.imageView2.animates = YES; // `SDAnimatedImageRep` can be used for built-in `NSImageView` to support better GIF & APNG rendering as well. No need `SDAnimatedImageView`
self.imageView3.animates = YES; // `SDAnimatedImageRep` can be used for built-in `NSImageView` to support better GIF & APNG rendering as well. No need `SDAnimatedImageView`
self.imageView4.animates = YES;

#pragma mark - Static Image
// NSImageView + Static Image
self.imageView1.sd_imageIndicator = SDWebImageProgressIndicator.defaultIndicator;
[self.imageView1 sd_setImageWithURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg"] placeholderImage:nil options:SDWebImageProgressiveLoad];
// SDAnimatedImageView + Static Image
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png"]];

#pragma mark - Animated Image
// NSImageView + Animated Image
self.imageView2.sd_imageIndicator = SDWebImageActivityIndicator.largeIndicator;
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/onevcat/APNGKit/2.2.0/Tests/APNGKitTests/Resources/General/APNG-cube.apng"]];
self.imageView3.sd_imageIndicator = SDWebImageActivityIndicator.largeIndicator;
[self.imageView3 sd_setImageWithURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/onevcat/APNGKit/2.2.0/Tests/APNGKitTests/Resources/General/APNG-cube.apng"]];
NSMenu *menu1 = [[NSMenu alloc] initWithTitle:@"Toggle Animation"];
NSMenuItem *item1 = [menu1 addItemWithTitle:@"Toggle Animation" action:@selector(toggleAnimation:) keyEquivalent:@""];
item1.tag = 1;
self.imageView2.menu = menu1;

// SDAnimatedImageView + Static Image
[self.imageView3 sd_setImageWithURL:[NSURL URLWithString:@"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png"]];

self.imageView3.menu = menu1;
// SDAnimatedImageView + Animated Image
self.imageView4.sd_imageTransition = SDWebImageTransition.fadeTransition;
self.imageView4.imageScaling = NSImageScaleProportionallyUpOrDown;
Expand All @@ -53,12 +71,36 @@ - (void)viewDidLoad {
item2.tag = 2;
self.imageView4.menu = menu2;

#pragma mark - HDR Image
// HDR Image
if (@available(macOS 14.0, *)) {
self.imageView5.preferredImageDynamicRange = NSImageDynamicRangeHigh;
self.imageView6.preferredImageDynamicRange = NSImageDynamicRangeHigh;
}
[self.imageView5 sd_setImageWithURL:[NSURL URLWithString:@"https://lightroom.adobe.com/v2c/spaces/113ab046f0d04b40aa7f8e10285961a7/assets/cd191116be514e1288ca6ea372303139/revisions/2749aff3294e404c9ffce3518e467d4a/renditions/99673919d096b42650b448f6516089cc.avif"] placeholderImage:nil options:0 context:@{SDWebImageContextImageDecodeToHDR : @(YES)}];
// SDR Image
[self.imageView6 sd_setImageWithURL:[NSURL URLWithString:@"https://lightroom.adobe.com/v2c/spaces/113ab046f0d04b40aa7f8e10285961a7/assets/cd191116be514e1288ca6ea372303139/revisions/2749aff3294e404c9ffce3518e467d4a/renditions/99673919d096b42650b448f6516089cc"] placeholderImage:nil options:0 context:@{SDWebImageContextImageDecodeToHDR : @(NO)}];

self.clearCacheButton.target = self;
self.clearCacheButton.action = @selector(clearCacheButtonClicked:);
[self.clearCacheButton sd_setImageWithURL:[NSURL URLWithString:@"https://png.icons8.com/color/100/000000/delete-sign.png"]];
[self.clearCacheButton sd_setAlternateImageWithURL:[NSURL URLWithString:@"https://png.icons8.com/color/100/000000/checkmark.png"]];
}

- (void)viewDidLayout {
[super viewDidLayout];
CGFloat space = 20;
CGFloat imageWidth = (self.view.frame.size.width - space * 4) / 3;
CGFloat imageHeight = (self.view.frame.size.height - space * 3) / 2;

self.imageView1.frame = CGRectMake(space * 1 + imageWidth * 0, space, imageWidth, imageHeight);
self.imageView2.frame = CGRectMake(self.imageView1.frame.origin.x, self.imageView1.frame.origin.y + imageHeight + space, imageWidth, imageHeight);
self.imageView3.frame = CGRectMake(space * 2 + imageWidth * 1, space, imageWidth, imageHeight);
self.imageView4.frame = CGRectMake(self.imageView3.frame.origin.x, self.imageView3.frame.origin.y + imageHeight + space, imageWidth, imageHeight);
self.imageView5.frame = CGRectMake(space * 3 + imageWidth * 2, space, imageWidth, imageHeight);
self.imageView6.frame = CGRectMake(self.imageView5.frame.origin.x, self.imageView5.frame.origin.y + imageHeight + space, imageWidth, imageHeight);
}

- (void)clearCacheButtonClicked:(NSResponder *)sender {
NSButton *button = (NSButton *)sender;
button.state = NSControlStateValueOn;
Expand All @@ -69,7 +111,7 @@ - (void)clearCacheButtonClicked:(NSResponder *)sender {
}

- (void)toggleAnimation:(NSMenuItem *)sender {
NSImageView *imageView = sender.tag == 1 ? self.imageView2 : self.imageView4;
NSImageView *imageView = sender.tag == 1 ? self.imageView3 : self.imageView4;
if (imageView.animates) {
imageView.animates = NO;
} else {
Expand Down
5 changes: 5 additions & 0 deletions SDWebImage/Core/SDGraphicsImageRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) {
/// A set of drawing attributes that represent the configuration of an image renderer context.
@interface SDGraphicsImageRendererFormat : NSObject

#if SD_UIKIT
/// The underlying uiformat for UIKit. This usage of this API should be careful, which may cause out of sync.
@property (nonatomic, strong, nonnull) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0));
#endif

/// The display scale of the image renderer context.
/// The default value is equal to the scale of the main screen.
@property (nonatomic) CGFloat scale;
Expand Down
39 changes: 3 additions & 36 deletions SDWebImage/Core/SDGraphicsImageRenderer.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@

#import "SDGraphicsImageRenderer.h"
#import "SDImageGraphics.h"

@interface SDGraphicsImageRendererFormat ()
#if SD_UIKIT
@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0));
#endif
@end
#import "SDDeviceHelper.h"

@implementation SDGraphicsImageRendererFormat
@synthesize scale = _scale;
Expand Down Expand Up @@ -131,21 +126,7 @@ - (instancetype)init {
self.uiformat = uiformat;
} else {
#endif
#if SD_VISION
CGFloat screenScale = UITraitCollection.currentTraitCollection.displayScale;
#elif SD_WATCH
CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif
CGFloat screenScale = SDDeviceHelper.screenScale;
self.scale = screenScale;
self.opaque = NO;
#if SD_UIKIT
Expand All @@ -172,21 +153,7 @@ - (instancetype)initForMainScreen {
self.uiformat = uiformat;
} else {
#endif
#if SD_VISION
CGFloat screenScale = UITraitCollection.currentTraitCollection.displayScale;
#elif SD_WATCH
CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif
CGFloat screenScale = SDDeviceHelper.screenScale;
self.scale = screenScale;
self.opaque = NO;
#if SD_UIKIT
Expand Down
9 changes: 9 additions & 0 deletions SDWebImage/Core/SDImageCacheDefine.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "SDAnimatedImage.h"
#import "UIImage+Metadata.h"
#import "SDInternalMacros.h"
#import "SDDeviceHelper.h"

#import <CoreServices/CoreServices.h>

Expand Down Expand Up @@ -49,6 +50,12 @@
mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:6];
}

// Some options need preserve the custom decode options
NSNumber *decodeToHDR = context[SDWebImageContextImageDecodeToHDR];
if (decodeToHDR == nil) {
decodeToHDR = mutableCoderOptions[SDImageCoderDecodeToHDR];
}

// Override individual options
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
Expand All @@ -57,6 +64,7 @@
mutableCoderOptions[SDImageCoderDecodeTypeIdentifierHint] = typeIdentifierHint;
mutableCoderOptions[SDImageCoderDecodeFileExtensionHint] = fileExtensionHint;
mutableCoderOptions[SDImageCoderDecodeScaleDownLimitBytes] = scaleDownLimitBytesValue;
mutableCoderOptions[SDImageCoderDecodeToHDR] = decodeToHDR;

return [mutableCoderOptions copy];
}
Expand All @@ -72,6 +80,7 @@ void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _Nonnull mutableCont
mutableContext[SDWebImageContextImagePreserveAspectRatio] = decodeOptions[SDImageCoderDecodePreserveAspectRatio];
mutableContext[SDWebImageContextImageThumbnailPixelSize] = decodeOptions[SDImageCoderDecodeThumbnailPixelSize];
mutableContext[SDWebImageContextImageScaleDownLimitBytes] = decodeOptions[SDImageCoderDecodeScaleDownLimitBytes];
mutableContext[SDWebImageContextImageDecodeToHDR] = decodeOptions[SDImageCoderDecodeToHDR];

NSString *typeIdentifierHint = decodeOptions[SDImageCoderDecodeTypeIdentifierHint];
if (!typeIdentifierHint) {
Expand Down
8 changes: 8 additions & 0 deletions SDWebImage/Core/SDImageCoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeUseLazyDec
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleDownLimitBytes;

/**
A Boolean value to provide converting to HDR during decoding.
@note Supported by iOS 17 and above when using ImageIO coder (third-party coder can support lower firmware)
Defaults to NO, decoder will automatically convert SDR.
@note works for `SDImageCoder`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeToHDR;

// These options are for image encoding
/**
A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need.
Expand Down
1 change: 1 addition & 0 deletions SDWebImage/Core/SDImageCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
SDImageCoderOption const SDImageCoderDecodeTypeIdentifierHint = @"decodeTypeIdentifierHint";
SDImageCoderOption const SDImageCoderDecodeUseLazyDecoding = @"decodeUseLazyDecoding";
SDImageCoderOption const SDImageCoderDecodeScaleDownLimitBytes = @"decodeScaleDownLimitBytes";
SDImageCoderOption const SDImageCoderDecodeToHDR = @"decodeToHDR";

SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
Expand Down
Loading

0 comments on commit f4f93c2

Please sign in to comment.