2012-08-01 14 views
13

Il mio problema principale è che ho bisogno di ottenere una miniatura per un oggetto ALAsset.Generazione di miniature personalizzate da ALAssetRepresentation

Ho provato un sacco di soluzioni e cercato overflow dello stack per giorni, tutte le soluzioni che ho trovato non funzionano per me a causa di questi vincoli:

  • non posso usare la miniatura di default perché è troppo poco ;
  • Non riesco a utilizzare l'immagine fullScreen o fullResolution perché ho molte immagini sullo schermo;
  • non posso usare UIImage o UIImageView per il ridimensionamento perché quei carichi l'immagine a piena risoluzione
  • non riesco a caricare l'immagine nella memoria, sto lavorando con le immagini 20Mpx;
  • Ho bisogno di creare una versione 200x200 px della risorsa originale da caricare sullo schermo;

questa è l'ultima iterazione del codice che è venuto con:

#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]; 

problema è, la risultante CGImageRef è né trasformare da orientamento, né della dimensione dei pixel massima specificata;

Ho anche cercato di trovare un modo di ridimensionamento utilizzando CGImageSource, ma:

  • l'url del risparmio non può essere utilizzato nel CGImageSourceCreateWithURL:;
  • non riesco a estrarre da ALAsset o ALAssetRepresentation a CGDataProviderRef da utilizzare con CGImageSourceCreateWithDataProvider:;
  • CGImageSourceCreateWithData: richiede che memorizzi la risorsa fullResolution o fullscreen in memoria per poter funzionare.

Mi manca qualcosa?

C'è un altro modo di ottenere una miniatura personalizzata da ALAsset o ALAssetRepresentation che mi manca?

Grazie in anticipo.

+2

+1. I documenti non sono corretti affermando che kCGImageSourceThumbnailMaxPixelSize funzionerà per questo. Non è così. – akaru

+0

vedere la mia risposta qui http://stackoverflow.com/questions/8116524/the-best-way-to-get-thumbnails-with-alassetslibrary/13598533#13598533 imposta la modalità di contenuto della visualizzazione delle immagini come UIViewContentModeScaleAspectFit ad esempio: imageView.contentMode = UIViewContentModeScaleAspectFit ; e usa dispatch_sync (dispatch_get_main_queue() per il file works.ALAssetsLibrary relativo all'interfaccia utente verrà eseguito in thread separato.Così suggerisco di fare gli elementi relativi all'interfaccia utente nella thread principale –

risposta

25

È possibile utilizzare CGImageSourceCreateThumbnailAtIndex per creare una piccola immagine da una sorgente di immagini potenzialmente grande. È possibile caricare l'immagine dal disco utilizzando il metodo e utilizzarlo per creare un CGImageSourceRef.

Poi basta passare le opzioni kCGImageSourceThumbnailMaxPixelSize e kCGImageSourceCreateThumbnailFromImageAlways al CGImageSourceCreateThumbnailAtIndex con la fonte immagine che hai creato, e creerà una versione più piccola per voi senza caricare la versione enorme nella memoria.

Ho scritto un blog post e gist con questa tecnica completamente sviluppata.

+0

è possibile creare una miniatura non quadrata? cioè 200 * 150 px? – alex

+1

@ alex, non penso che questo generi miniature minuscole.Il 'kCGImageSourceThumbnailMaxPixelSize' si riferisce alle dimensioni del lato più grande (larghezza o altezza), la miniatura avrà lo stesso aspetto dell'immagine originale. –

+0

thk, proverò – alex

4

C'è un problema con l'approccio this menzionato da Jesse Rusak.La vostra applicazione sarà precipitato con il seguente stack se attività è troppo grande:

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

E 'molto facile per simulare l'incidente. Leggiamo i dati di ALAssetRepresentation in getAssetBytesCallback con piccoli blocchi. La particolare dimensione del chunk non è importante. L'unica cosa che conta è chiamare il callback circa 20 volte.

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

Ecco linee finale del log

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

ho introdotto un contatore per evitare questo incidente. Puoi vedere la mia correzione qui sotto:

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

Sto cercando di replicare questo in Swift. Sto attraversando un periodo particolarmente difficile con i contesti CGDataProvider di cui ha bisogno. Qualche idea? – Unome

+0

Sembra che dovresti inserire questa implementazione in qualche classe Objective C e quindi usarla in Swift. È ovvio, ma non penso che ci sia un altro modo. –

Problemi correlati