从ALAssetRepresentation生成自定义缩略图

时间:2012-08-01 18:29:54

标签: ios thumbnails cgimage alassetslibrary alasset

我的主要问题是我需要获取ALAsset对象的缩略图。

我尝试了很多解决方案并搜索了堆栈溢出数天,由于这些限制,我找到的所有解决方案都不适用于我:

  • 我无法使用默认缩略图,因为它太少了;
  • 我无法使用fullScreen或fullResolution图像,因为我在屏幕上有很多图像;
  • 我无法使用UIImage或UIImageView进行大小调整,因为这些负载 fullResolution图像
  • 我无法将图像加载到内存中,我正在使用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调整大小的方法,但是:

  • 资产网址无法在CGImageSourceCreateWithURL:;
  • 中使用
  • 我无法从ALAssetALAssetRepresentation CGDataProviderRef中提取出CGImageSourceCreateWithDataProvider:;
  • CGImageSourceCreateWithData:要求我将fullResolution或fullscreen资源存储在内存中才能正常工作。

我错过了什么吗?

是否有另一种方法可以从ALAssetALAssetRepresentation获取我缺少的自定义缩略图?

提前致谢。

2 个答案:

答案 0 :(得分:25)

您可以使用CGImageSourceCreateThumbnailAtIndex从可能较大的图像源创建小图像。您可以使用ALAssetRepresentation的{​​{1}}方法从磁盘加载图像,并使用它创建CGImageSourceRef。

然后,您只需将getBytes:fromOffset:length:error:kCGImageSourceThumbnailMaxPixelSize选项与您创建的图像源一起传递给kCGImageSourceCreateThumbnailFromImageAlways,它将为您创建一个较小的版本,而无需加载巨大的版本到内存中。

我写了一个blog postgist这个技术充实了。

答案 1 :(得分:4)

this提到的Jesse Rusak方法存在问题。如果资产太大,您的应用程序将与以下堆栈崩溃:

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
...
  

链接寄存器分析:

     

符号:malloc + 52

     

描述:我们已经确定链接寄存器(lr)很可能包含帧#0的调用函数的返回地址,并已将其作为帧#1插入到崩溃线程的回溯中以帮助分析。通过应用启发式来确定崩溃功能是否可能在崩溃时创建了新的堆栈帧来进行此确定。

     

类型: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:关闭:2432 len:2156064

     

MRCloudApp(3461,0x701000)malloc:*** mach_vm_map(size = 217636864)失败(错误代码= 3)

     

***错误:无法分配区域

     

***在malloc_error_break中设置断点以进行调试

我介绍了一个防止这次崩溃的计数器。你可以在下面看到我的修复:

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;
}