Создание пользовательской миниатюры из 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
, который мне не хватает?
Спасибо заранее.
Ответы
Ответ 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;
}