diff --git a/iphone/Maps/Core/WebImage/IMWMImageCoder.h b/iphone/Maps/Core/WebImage/IMWMImageCoder.h new file mode 100644 index 0000000000..376de1f3e9 --- /dev/null +++ b/iphone/Maps/Core/WebImage/IMWMImageCoder.h @@ -0,0 +1,12 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol IMWMImageCoder + +- (UIImage * _Nullable)imageWithData:(NSData *)data; +- (NSData * _Nullable)dataFromImage:(UIImage *)image; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/Core/WebImage/MWMImageCache.h b/iphone/Maps/Core/WebImage/MWMImageCache.h index 9f5876e924..397bac1380 100644 --- a/iphone/Maps/Core/WebImage/MWMImageCache.h +++ b/iphone/Maps/Core/WebImage/MWMImageCache.h @@ -1,9 +1,13 @@ #import "IMWMImageCache.h" +#import "IMWMImageCoder.h" NS_ASSUME_NONNULL_BEGIN @interface MWMImageCache : NSObject +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithImageCoder:(id)imageCoder; + @end NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/Core/WebImage/MWMImageCache.m b/iphone/Maps/Core/WebImage/MWMImageCache.m index 8a376e9f84..5862989dab 100644 --- a/iphone/Maps/Core/WebImage/MWMImageCache.m +++ b/iphone/Maps/Core/WebImage/MWMImageCache.m @@ -9,18 +9,21 @@ static NSTimeInterval kCleanupTimeInterval = 30 * 24 * 60 * 60; @property (nonatomic, copy) NSString *cacheDirPath; @property (nonatomic, strong) dispatch_queue_t diskQueue; @property (nonatomic, strong) NSFileManager *fileManager; +@property (nonatomic, strong) id imageCoder; @end @implementation MWMImageCache -- (instancetype)init { +- (instancetype)initWithImageCoder:(id)imageCoder { self = [super init]; if (self) { _cache = [[NSCache alloc] init]; _cacheDirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"images"]; _diskQueue = dispatch_queue_create("mapsme.imageCache.disk", DISPATCH_QUEUE_SERIAL); _fileManager = [NSFileManager defaultManager]; + _imageCoder = imageCoder; + [_fileManager createDirectoryAtPath:_cacheDirPath withIntermediateDirectories:YES attributes:nil @@ -33,23 +36,24 @@ static NSTimeInterval kCleanupTimeInterval = 30 * 24 * 60 * 60; - (void)imageForKey:(NSString *)imageKey completion:(void (^)(UIImage *image, NSError *error))completion { UIImage *image = [self.cache objectForKey:imageKey]; if (image) { - completion(image, nil); // TODO: add error + completion(image, nil); } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *path = [self.cacheDirPath stringByAppendingPathComponent:imageKey.md5String]; __block NSData *imageData = nil; + __block NSError *error = nil; dispatch_sync(self.diskQueue, ^{ - imageData = [NSData dataWithContentsOfFile:path]; + imageData = [NSData dataWithContentsOfFile:path options:0 error:&error]; }); UIImage *image = nil; if (imageData) { - image = [UIImage imageWithData:imageData]; + image = [self.imageCoder imageWithData:imageData]; if (image) { [self.cache setObject:image forKey:imageKey]; } } dispatch_async(dispatch_get_main_queue(), ^{ - completion(image, nil); // TODO: add error + completion(image, error); }); }); } @@ -58,7 +62,7 @@ static NSTimeInterval kCleanupTimeInterval = 30 * 24 * 60 * 60; - (void)setImage:(UIImage *)image forKey:(NSString *)imageKey { [self.cache setObject:image forKey:imageKey]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSData *imageData = UIImageJPEGRepresentation(image, 0.9); + NSData *imageData = [self.imageCoder dataFromImage:image]; if (imageData) { NSString *path = [self.cacheDirPath stringByAppendingPathComponent:imageKey.md5String]; dispatch_sync(self.diskQueue, ^{ diff --git a/iphone/Maps/Core/WebImage/MWMImageCoder.h b/iphone/Maps/Core/WebImage/MWMImageCoder.h new file mode 100644 index 0000000000..d17f01bf0d --- /dev/null +++ b/iphone/Maps/Core/WebImage/MWMImageCoder.h @@ -0,0 +1,9 @@ +#import "IMWMImageCoder.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMImageCoder : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/Core/WebImage/MWMImageCoder.m b/iphone/Maps/Core/WebImage/MWMImageCoder.m new file mode 100644 index 0000000000..a7775af5f9 --- /dev/null +++ b/iphone/Maps/Core/WebImage/MWMImageCoder.m @@ -0,0 +1,51 @@ +#import "MWMImageCoder.h" + +@implementation MWMImageCoder + +- (UIImage *)imageWithData:(NSData *)data { + UIImage *image = [UIImage imageWithData:data]; + if (!image) { + return nil; + } + + CGImageRef cgImage = image.CGImage; + size_t width = CGImageGetWidth(cgImage); + size_t height = CGImageGetHeight(cgImage); + int32_t flags; + if ([self imageHasAlpha:image]) { + flags = kCGImageAlphaPremultipliedLast; + } else { + flags = kCGImageAlphaNoneSkipLast; + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, flags); + CGColorSpaceRelease(colorSpace); + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); + CGImageRef resultCgImage = CGBitmapContextCreateImage(context); + UIImage *resultImage = [UIImage imageWithCGImage:resultCgImage]; + + CGImageRelease(resultCgImage); + CGContextRelease(context); + + return resultImage; +} + +- (NSData *)dataFromImage:(UIImage *)image { + if ([self imageHasAlpha:image]) { + return UIImagePNGRepresentation(image); + } else { + return UIImageJPEGRepresentation(image, 0.9); + } +} + +- (BOOL)imageHasAlpha:(UIImage *)image { + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image.CGImage); + return (alphaInfo == kCGImageAlphaPremultipliedLast || + alphaInfo == kCGImageAlphaPremultipliedFirst || + alphaInfo == kCGImageAlphaLast || + alphaInfo == kCGImageAlphaFirst); +} + +@end diff --git a/iphone/Maps/Core/WebImage/MWMWebImage.h b/iphone/Maps/Core/WebImage/MWMWebImage.h index bebcd47d2e..dd6eb6e5d2 100644 --- a/iphone/Maps/Core/WebImage/MWMWebImage.h +++ b/iphone/Maps/Core/WebImage/MWMWebImage.h @@ -1,5 +1,6 @@ #import "IMWMWebImage.h" #import "IMWMImageCache.h" +#import "IMWMImageCoder.h" #import @@ -10,7 +11,8 @@ NS_ASSUME_NONNULL_BEGIN + (MWMWebImage *)defaultWebImage; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithImageCahce:(id)imageCache; +- (instancetype)initWithImageCahce:(id)imageCache + imageCoder:(id)imageCoder; - (id)imageWithUrl:(NSURL *)url completion:(MWMWebImageCompletion)completion; - (void)cleanup; diff --git a/iphone/Maps/Core/WebImage/MWMWebImage.m b/iphone/Maps/Core/WebImage/MWMWebImage.m index 42166a0ef5..e4dd887cd1 100644 --- a/iphone/Maps/Core/WebImage/MWMWebImage.m +++ b/iphone/Maps/Core/WebImage/MWMWebImage.m @@ -1,5 +1,6 @@ #import "MWMWebImage.h" #import "MWMImageCache.h" +#import "MWMImageCoder.h" @interface MWMWebImageTask : NSObject @@ -21,6 +22,7 @@ @property (nonatomic, strong) NSURLSession *urlSession; @property (nonatomic, strong) id imageCache; +@property (nonatomic, strong) id imageCoder; @end @@ -30,18 +32,22 @@ static MWMWebImage *instanse; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - instanse = [[self alloc] initWithImageCahce:[MWMImageCache new]]; + MWMImageCoder *coder = [MWMImageCoder new]; + instanse = [[self alloc] initWithImageCahce:[[MWMImageCache alloc] initWithImageCoder:coder] + imageCoder:coder]; }); return instanse; } -- (instancetype)initWithImageCahce:(id)imageCache { +- (instancetype)initWithImageCahce:(id)imageCache + imageCoder:(id)imageCoder { self = [super init]; if (self) { _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil]; _imageCache = imageCache; + _imageCoder = imageCoder; } return self; } @@ -62,7 +68,7 @@ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { UIImage *image = nil; if (data) { - image = [UIImage imageWithData:data]; + image = [self.imageCoder imageWithData:data]; if (image) { [self.imageCache setImage:image forKey:cacheKey]; } @@ -70,7 +76,7 @@ dispatch_async(dispatch_get_main_queue(), ^{ if (!imageTask.cancelled) { - completion(image, error); //TODO: replace error with generic error + completion(image, error); } }); }]; diff --git a/iphone/Maps/Core/WebImage/NSString+MD5.h b/iphone/Maps/Core/WebImage/NSString+MD5.h index b17e33ce96..a119c03f5d 100644 --- a/iphone/Maps/Core/WebImage/NSString+MD5.h +++ b/iphone/Maps/Core/WebImage/NSString+MD5.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSString (MD5) -- (NSString * _Nullable)md5String; +- (NSString *)md5String; @end diff --git a/iphone/Maps/Core/WebImage/NSString+MD5.m b/iphone/Maps/Core/WebImage/NSString+MD5.m index a8ea3b9f99..d1e544feb0 100644 --- a/iphone/Maps/Core/WebImage/NSString+MD5.m +++ b/iphone/Maps/Core/WebImage/NSString+MD5.m @@ -7,7 +7,7 @@ - (NSString *)md5String { NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; if (data.length == 0) { - return nil; + return @""; } unsigned char buf[CC_MD5_DIGEST_LENGTH]; diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 5f1f2ef9a9..b4d64a7f95 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -427,6 +427,7 @@ 47E6CB0B2178BA3600EA102B /* SearchBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E6CB092178BA3600EA102B /* SearchBannerCell.swift */; }; 47E6CB0C2178BA3600EA102B /* SearchBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47E6CB0A2178BA3600EA102B /* SearchBannerCell.xib */; }; 47EF05B321504D8F00EAC269 /* RemoveAdsPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EF05B221504D8F00EAC269 /* RemoveAdsPresentationController.swift */; }; + 47F67D1521CAB21B0069754E /* MWMImageCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 47F67D1421CAB21B0069754E /* MWMImageCoder.m */; }; 47F86CFF20C936FC00FEE291 /* TabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F86CFE20C936FC00FEE291 /* TabView.swift */; }; 47F86D0120C93D8D00FEE291 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F86D0020C93D8D00FEE291 /* TabViewController.swift */; }; 4A300ED51C6DCFD400140018 /* countries-strings in Resources */ = {isa = PBXBuildFile; fileRef = 4A300ED31C6DCFD400140018 /* countries-strings */; }; @@ -1468,6 +1469,9 @@ 47E6CB0A2178BA3600EA102B /* SearchBannerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchBannerCell.xib; sourceTree = ""; }; 47EF05B221504D8F00EAC269 /* RemoveAdsPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAdsPresentationController.swift; sourceTree = ""; }; 47F67D0F21CA8F800069754E /* IMWMWebImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IMWMWebImage.h; sourceTree = ""; }; + 47F67D1321CAB21B0069754E /* MWMImageCoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMImageCoder.h; sourceTree = ""; }; + 47F67D1421CAB21B0069754E /* MWMImageCoder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MWMImageCoder.m; sourceTree = ""; }; + 47F67D1621CAB50B0069754E /* IMWMImageCoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IMWMImageCoder.h; sourceTree = ""; }; 47F86CFE20C936FC00FEE291 /* TabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabView.swift; sourceTree = ""; }; 47F86D0020C93D8D00FEE291 /* TabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; 4A00DBDE1AB704C400113624 /* drules_proto_dark.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = drules_proto_dark.bin; path = ../../data/drules_proto_dark.bin; sourceTree = ""; }; @@ -3432,6 +3436,9 @@ 47B9065021C7FA3F0079C85E /* NSString+MD5.m */, 47B9064E21C7FA3E0079C85E /* UIImageView+WebImage.h */, 47B9064F21C7FA3E0079C85E /* UIImageView+WebImage.m */, + 47F67D1621CAB50B0069754E /* IMWMImageCoder.h */, + 47F67D1321CAB21B0069754E /* MWMImageCoder.h */, + 47F67D1421CAB21B0069754E /* MWMImageCoder.m */, ); path = WebImage; sourceTree = ""; @@ -5215,6 +5222,7 @@ 34AB661A1FC5AA330078E451 /* MWMTaxiCollectionLayout.mm in Sources */, 345C2F8A1F86361B009DB8B4 /* MWMUGCViewModel.mm in Sources */, 33F8BA4E2199AB9500ECA8EE /* TagsDataSource.swift in Sources */, + 47F67D1521CAB21B0069754E /* MWMImageCoder.m in Sources */, 34AB66861FC5AA330078E451 /* MWMNavigationInfoView.mm in Sources */, 34C9BD051C6DB693000DC38D /* MWMViewController.mm in Sources */, 331630D12191D74B00BB91A9 /* TagSectionHeaderView.swift in Sources */,