13

我的主要问题是我需要获取 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,但是:

  • 资产 url 不能用于CGImageSourceCreateWithURL:;
  • 我无法从 中提取ALAssetALAssetRepresentationCGDataProviderRef一起使用CGImageSourceCreateWithDataProvider:
  • CGImageSourceCreateWithData:需要我将 fullResolution 或全屏资产存储在内存中才能工作。

我错过了什么吗?

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

提前致谢。

4

2 回答 2

25

您可以使用CGImageSourceCreateThumbnailAtIndex从可能很大的图像源创建小图像。您可以使用ALAssetRepresentation'getBytes:fromOffset:length:error:方法从磁盘加载图像,并使用它来创建 CGImageSourceRef。

然后,您只需将kCGImageSourceThumbnailMaxPixelSizekCGImageSourceCreateThumbnailFromImageAlways选项传递给CGImageSourceCreateThumbnailAtIndex您创建的图像源,它将为您创建一个较小的版本,而无需将大版本加载到内存中。

我已经写了一篇博客文章要点,充分充实了这种技术。

于 2012-12-18T21:00:53.987 回答
4

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 长度: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;
}
于 2014-03-21T11:53:32.863 回答