VLCThumbnailsCache.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*****************************************************************************
  2. * VLCThumbnailsCache.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Gleb Pinigin <gpinigin # gmail.com>
  9. * Felix Paul Kühne <fkuehne # videolan.org>
  10. * Carola Nitz <caro # videolan.org>
  11. *
  12. * Refer to the COPYING file of the official project for license.
  13. *****************************************************************************/
  14. #import "VLC for iOS-Prefix.pch"
  15. #import "VLCThumbnailsCache.h"
  16. #import <CommonCrypto/CommonDigest.h>
  17. #import "UIImage+Blur.h"
  18. #import "UIImage+Scaling.h"
  19. #import <WatchKit/WatchKit.h>
  20. @interface VLCThumbnailsCache() {
  21. NSInteger MaxCacheSize;
  22. NSCache *_thumbnailCache;
  23. NSCache *_thumbnailCacheMetadata;
  24. NSInteger _currentDeviceIdiom;
  25. }
  26. @end
  27. @implementation VLCThumbnailsCache
  28. #define MAX_CACHE_SIZE_IPHONE 21 // three times the number of items shown on iPhone 5
  29. #define MAX_CACHE_SIZE_IPAD 27 // three times the number of items shown on iPad
  30. #define MAX_CACHE_SIZE_WATCH 15 // three times the number of items shown on 42mm Watch
  31. - (instancetype)init
  32. {
  33. self = [super init];
  34. if (self) {
  35. _currentDeviceIdiom = [[UIDevice currentDevice] userInterfaceIdiom];
  36. MaxCacheSize = 0;
  37. switch (_currentDeviceIdiom) {
  38. case UIUserInterfaceIdiomPad:
  39. MaxCacheSize = MAX_CACHE_SIZE_IPAD;
  40. break;
  41. case UIUserInterfaceIdiomPhone:
  42. MaxCacheSize = MAX_CACHE_SIZE_IPHONE;
  43. break;
  44. default:
  45. MaxCacheSize = MAX_CACHE_SIZE_WATCH;
  46. break;
  47. }
  48. _thumbnailCache = [[NSCache alloc] init];
  49. _thumbnailCacheMetadata = [[NSCache alloc] init];
  50. [_thumbnailCache setCountLimit: MaxCacheSize];
  51. [_thumbnailCacheMetadata setCountLimit: MaxCacheSize];
  52. }
  53. return self;
  54. }
  55. + (instancetype)sharedThumbnailCache
  56. {
  57. static dispatch_once_t onceToken;
  58. static VLCThumbnailsCache *sharedThumbnailCache;
  59. dispatch_once(&onceToken, ^{
  60. sharedThumbnailCache = [[VLCThumbnailsCache alloc] init];
  61. });
  62. return sharedThumbnailCache;
  63. }
  64. + (UIImage *)thumbnailForManagedObject:(NSManagedObject *)object
  65. {
  66. UIImage *thumbnail;
  67. VLCThumbnailsCache *cache = [VLCThumbnailsCache sharedThumbnailCache];
  68. if ([object isKindOfClass:[MLShow class]]) {
  69. thumbnail = [cache thumbnailForShow:(MLShow *)object];
  70. } else if ([object isKindOfClass:[MLShowEpisode class]]) {
  71. MLFile *anyFileFromEpisode = [(MLShowEpisode *)object files].anyObject;
  72. thumbnail = [cache thumbnailForMediaFile:anyFileFromEpisode];
  73. } else if ([object isKindOfClass:[MLLabel class]]) {
  74. thumbnail = [cache thumbnailForLabel:(MLLabel *)object];
  75. } else if ([object isKindOfClass:[MLAlbum class]]) {
  76. MLFile *anyFileFromAnyTrack = [[(MLAlbum *)object tracks].anyObject files].anyObject;
  77. thumbnail = [cache thumbnailForMediaFile:anyFileFromAnyTrack];
  78. } else if ([object isKindOfClass:[MLAlbumTrack class]]) {
  79. MLFile *anyFileFromTrack = [(MLAlbumTrack *)object files].anyObject;
  80. thumbnail = [cache thumbnailForMediaFile:anyFileFromTrack];
  81. } else {
  82. thumbnail = [cache thumbnailForMediaFile:(MLFile *)object];
  83. }
  84. return thumbnail;
  85. }
  86. + (UIImage *)thumbnailForManagedObject:(NSManagedObject *)object toFitRect:(CGRect)rect shouldReplaceCache:(BOOL)replaceCache
  87. {
  88. UIImage *rawThumbnail = [self thumbnailForManagedObject:object];
  89. CGSize rawSize = rawThumbnail.size;
  90. /* scaling is potentially expensive, so we should avoid re-doing it for the same size over and over again */
  91. if (rawSize.width <= rect.size.width && rawSize.height <= rect.size.height)
  92. return rawThumbnail;
  93. UIImage *scaledImage = [UIImage scaleImage:rawThumbnail toFitRect:rect];
  94. if (replaceCache)
  95. [[VLCThumbnailsCache sharedThumbnailCache] _setThumbnail:scaledImage forObjectId:object.objectID];
  96. return scaledImage;
  97. }
  98. - (void)_setThumbnail:(UIImage *)image forObjectId:(NSManagedObjectID *)objID
  99. {
  100. if (image)
  101. [_thumbnailCache setObject:image forKey:objID];
  102. }
  103. - (UIImage *)thumbnailForMediaFile:(MLFile *)mediaFile
  104. {
  105. if (mediaFile == nil || mediaFile.objectID == nil)
  106. return nil;
  107. NSManagedObjectID *objID = mediaFile.objectID;
  108. UIImage *displayedImage = [_thumbnailCache objectForKey:objID];
  109. if (displayedImage)
  110. return displayedImage;
  111. if (!displayedImage)
  112. displayedImage = mediaFile.computedThumbnail;
  113. if (displayedImage)
  114. [_thumbnailCache setObject:displayedImage forKey:objID];
  115. return displayedImage;
  116. }
  117. - (UIImage *)thumbnailForShow:(MLShow *)mediaShow
  118. {
  119. NSManagedObjectID *objID = mediaShow.objectID;
  120. UIImage *displayedImage;
  121. BOOL forceRefresh = NO;
  122. NSUInteger count = [mediaShow.episodes count];
  123. NSNumber *previousCount = [_thumbnailCacheMetadata objectForKey:objID];
  124. if (previousCount.unsignedIntegerValue != count)
  125. forceRefresh = YES;
  126. if (!forceRefresh) {
  127. displayedImage = [_thumbnailCache objectForKey:objID];
  128. if (displayedImage)
  129. return displayedImage;
  130. }
  131. NSUInteger fileNumber = count > 3 ? 3 : count;
  132. NSArray *episodes = [mediaShow.episodes allObjects];
  133. NSMutableArray *files = [[NSMutableArray alloc] init];
  134. for (NSUInteger x = 0; x < count; x++) {
  135. /* this is a multi-threaded app, so the episode object might be there already,
  136. * but without an assigned file, so we need to check for its existance (#13128) */
  137. if ([episodes[x] files].anyObject != nil)
  138. [files addObject:[episodes[x] files].anyObject];
  139. }
  140. displayedImage = [self clusterThumbFromFiles:files andNumber:fileNumber blur:NO];
  141. if (displayedImage) {
  142. [_thumbnailCache setObject:displayedImage forKey:objID];
  143. [_thumbnailCacheMetadata setObject:@(count) forKey:objID];
  144. }
  145. return displayedImage;
  146. }
  147. - (UIImage *)thumbnailForLabel:(MLLabel *)mediaLabel
  148. {
  149. NSManagedObjectID *objID = mediaLabel.objectID;
  150. UIImage *displayedImage;
  151. BOOL forceRefresh = NO;
  152. NSUInteger count = [mediaLabel.files count];
  153. NSNumber *previousCount = [_thumbnailCacheMetadata objectForKey:objID];
  154. if (previousCount.unsignedIntegerValue != count)
  155. forceRefresh = YES;
  156. if (!forceRefresh) {
  157. displayedImage = [_thumbnailCache objectForKey:objID];
  158. if (displayedImage)
  159. return displayedImage;
  160. }
  161. NSUInteger fileNumber = count > 3 ? 3 : count;
  162. NSArray *files = [mediaLabel.files allObjects];
  163. BOOL blur = NO;
  164. if (SYSTEM_RUNS_IOS7_OR_LATER)
  165. blur = YES;
  166. displayedImage = [self clusterThumbFromFiles:files andNumber:fileNumber blur:blur];
  167. if (displayedImage) {
  168. [_thumbnailCache setObject:displayedImage forKey:objID];
  169. [_thumbnailCacheMetadata setObject:@(count) forKey:objID];
  170. }
  171. return displayedImage;
  172. }
  173. - (UIImage *)clusterThumbFromFiles:(NSArray *)files andNumber:(NSUInteger)fileNumber blur:(BOOL)blurImage
  174. {
  175. UIImage *clusterThumb;
  176. CGSize imageSize;
  177. if (_currentDeviceIdiom == UIUserInterfaceIdiomPad) {
  178. if ([UIScreen mainScreen].scale==2.0)
  179. imageSize = CGSizeMake(682., 384.);
  180. else
  181. imageSize = CGSizeMake(341., 192.);
  182. } else if (_currentDeviceIdiom == UIUserInterfaceIdiomPhone) {
  183. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  184. if ([UIScreen mainScreen].scale==2.0)
  185. imageSize = CGSizeMake(480., 270.);
  186. else
  187. imageSize = CGSizeMake(720., 405.);
  188. } else {
  189. if ([UIScreen mainScreen].scale==2.0)
  190. imageSize = CGSizeMake(258., 145.);
  191. else
  192. imageSize = CGSizeMake(129., 73.);
  193. }
  194. } else {
  195. if (SYSTEM_RUNS_IOS82_OR_LATER) {
  196. if (WKInterfaceDevice.currentDevice != nil) {
  197. CGRect screenRect = WKInterfaceDevice.currentDevice.screenBounds;
  198. imageSize = CGSizeMake(screenRect.size.width * WKInterfaceDevice.currentDevice.screenScale, 120.);
  199. }
  200. }
  201. }
  202. UIGraphicsBeginImageContext(imageSize);
  203. NSUInteger iter = files.count < fileNumber ? files.count : fileNumber;
  204. for (NSUInteger i = 0; i < iter; i++) {
  205. MLFile *file = [files objectAtIndex:i];
  206. clusterThumb = [self thumbnailForMediaFile:file];
  207. CGContextRef context = UIGraphicsGetCurrentContext();
  208. CGFloat imagePartWidth = (imageSize.width / iter);
  209. //the rect in which the image should be drawn
  210. CGRect clippingRect = CGRectMake(imagePartWidth * i, 0, imagePartWidth, imageSize.height);
  211. CGContextSaveGState(context);
  212. CGContextClipToRect(context, clippingRect);
  213. //take the center of the clippingRect and calculate the offset from the original center
  214. CGFloat centerOffset = (imagePartWidth * i + imagePartWidth / 2) - imageSize.width / 2;
  215. //shift the rect to draw the middle of the image in the clippingrect
  216. CGRect drawingRect = CGRectMake(centerOffset, 0, imageSize.width, imageSize.height);
  217. [clusterThumb drawInRect:drawingRect];
  218. //get rid of the old clippingRect
  219. CGContextRestoreGState(context);
  220. }
  221. clusterThumb = UIGraphicsGetImageFromCurrentImageContext();
  222. UIGraphicsEndImageContext();
  223. if (!blurImage)
  224. return clusterThumb;
  225. return [UIImage applyBlurOnImage:clusterThumb withRadius:0.1];
  226. }
  227. @end