Подтвердить что ты не робот

Создание пользовательской миниатюры из ALAssetRepresentation

Моя основная проблема: мне нужно получить миниатюру для объекта 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:;
  • Я не могу извлечь из ALAsset или ALAssetRepresentation a CGDataProviderRef для использования с CGImageSourceCreateWithDataProvider:;
  • CGImageSourceCreateWithData: требует, чтобы я сохранил полноразмерный или полноэкранный актив в памяти, чтобы работать.

Мне что-то не хватает?

Есть ли другой способ получения пользовательского эскиза из ALAsset или ALAssetRepresentation, который мне не хватает?

Спасибо заранее.

4b9b3361

Ответ 1

Вы можете использовать CGImageSourceCreateThumbnailAtIndex для создания небольшого изображения из потенциально большого источника изображения. Вы можете загрузить изображение с диска с помощью метода ALAssetRepresentation getBytes:fromOffset:length:error: и использовать его для создания CGImageSourceRef.

Затем вам просто нужно передать параметры kCGImageSourceThumbnailMaxPixelSize и kCGImageSourceCreateThumbnailFromImageAlways в CGImageSourceCreateThumbnailAtIndex с созданным источником изображения, и он создаст для вас меньшую версию без загрузки огромной версии в память.

Я написал сообщение и gist с этой техникой, полностью раскрытой.

Ответ 2

Существует проблема с этот подход, упомянутый Джесси Русак. Ваше приложение будет разбито со следующим стеком, если актив слишком велик:

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

Очень легко имитировать крушение. Позвольте читать данные из ALAssetRepresentation в getAssetBytesCallback с небольшими кусками. Конкретный размер куска не важен. Единственное, что имеет значение, - вызов callback примерно 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 (размер = 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;
}