2012-08-01 9 views
13

Mein Hauptproblem ist, dass ich eine Miniaturansicht für ein ALAsset-Objekt erhalten muss.Generieren von benutzerdefinierten Miniaturansichten aus ALAssetRepresentation

habe ich versucht, eine Menge von Lösungen und nach Tagen Stack-Überlauf, alle Lösungen i sind nicht für mich auf diese Einschränkung durch Arbeit gefunden:

  • ich nicht das Standard-Miniaturbild verwenden kann, weil es zu wenig ist ;
  • Ich kann das Bild fullScreen oder fullResolution nicht verwenden, weil ich viele Bilder auf dem Bildschirm habe;
  • Ich kann verwenden UIImage oder UIImageView nicht zum Ändern der Größe, da diese Lasten die fullResolution Bild
  • ich das Bild im Speicher nicht geladen werden kann, arbeite ich mit 20Mpx Bildern;
  • Ich muss eine 200x200 px Version des ursprünglichen Assets erstellen, um es auf dem Bildschirm zu laden;

ist dies die letzte Iteration des Code i mit kam:

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

Problem ist, wird das resultierende CGImageRef weder durch Orientierung transformiert, noch der angegebenen max Pixelgröße;

Ich habe auch versucht CGImageSource, wobei jedoch eine Möglichkeit, Ändern der Größe zu finden:

  • die Asset-URL kann nicht in den CGImageSourceCreateWithURL: verwendet;
  • ich kann nicht aus ALAsset oder ALAssetRepresentation eine CGDataProviderRef mit CGImageSourceCreateWithDataProvider: zu verwenden;
  • CGImageSourceCreateWithData: erfordert, dass ich die fullResolution- oder Vollbild-Assets im Arbeitsspeicher speichern muss, damit sie funktionieren.

Fehle ich etwas?

Gibt es eine andere Möglichkeit, eine benutzerdefinierte Miniaturansicht von ALAsset oder ALAssetRepresentation zu erhalten, die ich vermisse?

Vielen Dank im Voraus.

+2

+1. Die Dokumente stimmen nicht damit überein, dass kCGImageSourceThumbnailMaxPixelSize dafür geeignet ist. Es tut nicht. – akaru

+0

siehe meine Antwort hier http://stackoverflow.com/questions/8116524/the-best-way-to-get-thumbnails-with-alassetslibrary/13598533#13598533 Setzen Sie Ihren Imageview Content-Modus als UIViewContentModeScaleAspectFit zB: imageView.contentMode = UIViewContentModeScaleAspectFit ; und benutze dispatch_sync (dispatch_get_main_queue() für UI related works. ALAssetsLibrary Block wird in einem separaten Thread ausgeführt. Also ich schlage vor, die UI bezogenen Sachen im Hauptthread –

Antwort

25

Sie können CGImageSourceCreateThumbnailAtIndex verwenden, um ein kleines Bild aus einer potenziell großen Bildquelle zu erstellen. Sie können Ihr Image mit der Methode ALAssetRepresentationgetBytes:fromOffset:length:error: von der Festplatte laden und daraus ein CGImageSourceRef erstellen.

Dann brauchen Sie nur die kCGImageSourceThumbnailMaxPixelSize und kCGImageSourceCreateThumbnailFromImageAlways Optionen CGImageSourceCreateThumbnailAtIndex mit der Bildquelle zu übergeben Sie erstellt haben, und es wird eine kleinere Version für Sie erstellen, ohne die große Version in den Speicher geladen werden.

Ich habe eine blog post und gist mit dieser Technik vollständig ausgearbeitet geschrieben.

+0

ist es möglich, nicht quadratische thumbnail? dh 200 * 150 px? – alex

+1

@ alex, ich glaube nicht, dass dies quadratische Thumbnails erzeugt. Die 'kCGImageSourceThumbnailMaxPixelSize' bezieht sich auf die Größe der größeren Seite (Breite oder Höhe); das Thumbnail wird den gleichen Aspekt wie das Originalbild haben. –

+0

thk, ich werde es versuchen – alex

4

Es gibt ein Problem mit this Ansatz erwähnt von Jesse Rusak.Ihre App wird mit dem folgenden Stack zum Absturz gebracht werden, wenn Vermögenswert zu groß ist:

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

Es ist sehr einfach den Absturz zu simulieren. Lassen Sie uns Daten von ALAssetRepresentation in getAssetBytesCallback mit kleinen Chunks lesen. Die bestimmte Größe des Chunks ist nicht wichtig. Das einzige, was zählt, ist Callback etwa 20 Mal aufrufen.

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

Hier sind Schwanz Zeilen des Protokolls

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

ich einen Zähler eingeführt diesen Absturz zu verhindern. Sie können meinen Fix unten sehen:

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

Ich versuche, dies in Swift zu replizieren. Ich habe eine besonders harte Zeit mit den CGDataProvider-Kontexten, die es benötigt. Irgendwelche Ideen? – Unome

+0

Es sieht so aus, dass Sie diese Implementierung in eine Objective C-Klasse einbinden und dann in Swift verwenden sollten. Das ist offensichtlich, aber ich glaube nicht, dass es anders geht. –