2012-08-01 80 views
13

我的主要問題是我需要獲取ALAsset對象的縮略圖。從ALAssetRepresentation生成自定義縮略圖

我嘗試了很多的解決方案,並尋找天堆棧溢出,所有的解決方案,我發現工作不適合我,由於這些約束:

  • 我不能使用默認的縮略圖,因爲它是太少;
  • 我無法使用fullScreen或fullResolution圖像,因爲屏幕上有很多圖像;
  • 我不能使用的UIImage或UIImageView的調整大小,因爲這些負載 的全分辨率圖像
  • 我無法加載在內存中的圖像,我與20Mpx圖像工作;
  • 我需要創建一個200x200像素版本的原始資產加載到屏幕上;

這是我來的代碼的最後一次迭代:

#import <AssetsLibrary/ALAsset.h> 
#import <ImageIO/ImageIO.h> 

// ... 

ALAsset *asset; 

// ... 

ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation]; 

NSDictionary *thumbnailOptions = [NSDictionary dictionaryWithObjectsAndKeys: 
    (id)kCFBooleanTrue, kCGImageSourceCreateThumbnailWithTransform, 
    (id)kCFBooleanTrue, kCGImageSourceCreateThumbnailFromImageAlways, 
    (id)[NSNumber numberWithFloat:200], kCGImageSourceThumbnailMaxPixelSize, 
    nil]; 

CGImageRef generatedThumbnail = [assetRepresentation CGImageWithOptions:thumbnailOptions]; 

UIImage *thumbnailImage = [UIImage imageWithCGImage:generatedThumbnail]; 

問題是,所得到的CGImageRef既不由取向改變,也沒有指定的最大像素大小的;

我也試圖找到利用CGImageSource調整的一種方式,但:

  • 資產URL不能在CGImageSourceCreateWithURL:使用;
  • 我不能從ALAssetALAssetRepresentation提取一個CGDataProviderRefCGImageSourceCreateWithDataProvider:一起使用;
  • CGImageSourceCreateWithData:要求我將fullResolution或全屏資產存儲在內存中才能正常工作。

我錯過了什麼嗎?

是否有另一種方法從ALAssetALAssetRepresentation獲取自定義縮略圖,我錯過了?

在此先感謝。

+2

+1。該文檔是不正確的,說明kCGImageSourceThumbnailMaxPixelSize將適用於此。它不是。 – akaru 2012-09-05 23:06:06

+0

看到我的答案在這裏http://stackoverflow.com/questions/8116524/the-best-way-to-get-thumbnails-with-alassetslibrary/13598533#13598533設置你的ImageView的內容模式UIViewContentModeScaleAspectFit如:imageView.contentMode = UIViewContentModeScaleAspectFit ;並使用dispatch_sync(dispatch_get_main_queue()的UI相關的works.ALAssetsLibrary塊將在單獨的線程中執行。因此,我建議做UI相關的東西在主線程 – 2012-12-04 11:31:57

回答

25

您可以使用CGImageSourceCreateThumbnailAtIndex從潛在的大圖像源創建一個小圖像。您可以使用ALAssetRepresentationgetBytes:fromOffset:length:error:方法從磁盤加載映像,並使用它創建CGImageSourceRef。

然後你只需要通過kCGImageSourceThumbnailMaxPixelSizekCGImageSourceCreateThumbnailFromImageAlways選項CGImageSourceCreateThumbnailAtIndex與你所創建的圖像源,它會爲你創建一個更小的版本,而無需加載巨大的版本到內存中。

我寫了一個blog postgist這一技術全面充實。

+0

是有可能創造不是正方形縮略圖?即200 * 150像素? – alex 2013-10-22 14:33:01

+1

@亞歷克斯,我不認爲這會產生方形縮略圖,'kCGImageSourceThumbnailMaxPixelSize'指的是較大邊(寬度或高度)的大小;縮略圖的方位與原始圖像相同 – 2013-10-22 15:33:42

+0

thk,我會試試 – alex 2013-10-23 05:10:44

4

有通過Jesse Rusak提到與this方法的問題。您的應用程序將與以下堆棧崩潰,如果資產是太大:

0 CoreGraphics    0x2f602f1c x_malloc + 16 
1 libsystem_malloc.dylib 0x39fadd63 malloc + 52 
2 CoreGraphics    0x2f62413f CGDataProviderCopyData + 178 
3 ImageIO     0x302e27b7 CGImageReadCreateWithProvider + 156 
4 ImageIO     0x302e2699 CGImageSourceCreateWithDataProvider + 180 
... 

Link Register Analysis:

Symbol: malloc + 52

Description: We have determined that the link register (lr) is very likely to contain the return address of frame #0's calling function, and have inserted it into the crashing thread's backtrace as frame #1 to aid in analysis. This determination was made by applying a heuristic to determine whether the crashing function was likely to have created a new stack frame at the time of the crash.

Type: 1

這是很容易模仿的崩潰。讓我們用小塊讀取來自getAssetBytesCallback中ALAssetRepresentation的數據。塊的特定大小並不重要。唯一重要的是調用回調約20次。

static size_t getAssetBytesCallback(void *info, void *buffer, off_t position, size_t count) { 
    static int i = 0; ++i; 
    ALAssetRepresentation *rep = (__bridge id)info; 
    NSError *error = nil; 
    NSLog(@"%d: off:%lld len:%zu", i, position, count); 
    const size_t countRead = [rep getBytes:(uint8_t *)buffer fromOffset:position length:128 error:&error]; 
    return countRead; 
} 

這裏是日誌

2014-03-21 11:21:14.250 MRCloudApp[3461:1303] 20: off:2432 len:2156064

MRCloudApp(3461,0x701000) malloc: *** mach_vm_map(size=217636864) failed (error code=3)

*** error: can't allocate region

*** set a breakpoint in malloc_error_break to debug

我介紹了一個計數器來防止這種崩潰的尾部線條。您可以在下面看到我的解決辦法:

typedef struct { 
    void *assetRepresentation; 
    int decodingIterationCount; 
} ThumbnailDecodingContext; 
static const int kThumbnailDecodingContextMaxIterationCount = 16; 

static size_t getAssetBytesCallback(void *info, void *buffer, off_t position, size_t count) { 
    ThumbnailDecodingContext *decodingContext = (ThumbnailDecodingContext *)info; 
    ALAssetRepresentation *assetRepresentation = (__bridge ALAssetRepresentation *)decodingContext->assetRepresentation; 
    if (decodingContext->decodingIterationCount == kThumbnailDecodingContextMaxIterationCount) { 
     NSLog(@"WARNING: Image %@ is too large for thumbnail extraction.", [assetRepresentation url]); 
     return 0; 
    } 
    ++decodingContext->decodingIterationCount; 
    NSError *error = nil; 
    size_t countRead = [assetRepresentation getBytes:(uint8_t *)buffer fromOffset:position length:count error:&error]; 
    if (countRead == 0 || error != nil) { 
     NSLog(@"ERROR: Failed to decode image %@: %@", [assetRepresentation url], error); 
     return 0; 
    } 
    return countRead; 
} 

- (UIImage *)thumbnailForAsset:(ALAsset *)asset maxPixelSize:(CGFloat)size { 
    NSParameterAssert(asset); 
    NSParameterAssert(size > 0); 
    ALAssetRepresentation *representation = [asset defaultRepresentation]; 
    if (!representation) { 
     return nil; 
    } 
    CGDataProviderDirectCallbacks callbacks = { 
     .version = 0, 
     .getBytePointer = NULL, 
     .releaseBytePointer = NULL, 
     .getBytesAtPosition = getAssetBytesCallback, 
     .releaseInfo = NULL 
    }; 
    ThumbnailDecodingContext decodingContext = { 
     .assetRepresentation = (__bridge void *)representation, 
     .decodingIterationCount = 0 
    }; 
    CGDataProviderRef provider = CGDataProviderCreateDirect((void *)&decodingContext, [representation size], &callbacks); 
    NSParameterAssert(provider); 
    if (!provider) { 
     return nil; 
    } 
    CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL); 
    NSParameterAssert(source); 
    if (!source) { 
     CGDataProviderRelease(provider); 
     return nil; 
    } 
    CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef) @{(NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES, 
                             (NSString *)kCGImageSourceThumbnailMaxPixelSize   : [NSNumber numberWithFloat:size], 
                             (NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES}); 
    UIImage *image = nil; 
    if (imageRef) { 
     image = [UIImage imageWithCGImage:imageRef]; 
     CGImageRelease(imageRef); 
    } 
    CFRelease(source); 
    CGDataProviderRelease(provider); 
    return image; 
} 
+0

我想在Swift中複製這個。我需要特別難處理的CGDataProvider上下文。有任何想法嗎? – Unome 2015-08-24 19:31:09

+0

看起來你應該將這個實現包裝到Objective C類中,然後在Swift中使用它。這很明顯,但我認爲沒有其他辦法。 – 2015-08-25 11:55:36