2014-04-10 17 views
11

Il problema della decompressione dell'immagine è stato molto discusso in Stack Overflow ma fino a questa domanda c'erano 0 citazioni di kCGImageSourceShouldCacheImmediately, un'opzione introdotta in iOS 7 che, in teoria, si occupa di questo problema. Dalle intestazioni:Decompressione dell'immagine in iOS 7

Specifica se la decodifica delle immagini e la memorizzazione nella cache dovrebbe avvenire al momento della creazione dell'immagine.

In Objc.io #7 Peter Steinberger ha suggerito questo approccio:

+ (UIImage *)decompressedImageWithData:(NSData *)data 
{ 
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); 
    CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES}); 

    UIImage *image = [UIImage imageWithCGImage:cgImage]; 
    CGImageRelease(cgImage); 
    CFRelease(source); 
    return image; 
} 

librerie come AFNetworking e SDWebImage ancora fare la decompressione delle immagini con il metodo CGContextDrawImage. Da SDWebImage:

+ (UIImage *)decodedImageWithImage:(UIImage *)image { 
    if (image.images) { 
     // Do not decode animated images 
     return image; 
    } 

    CGImageRef imageRef = image.CGImage; 
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); 
    CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; 

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 

    int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); 
    BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || 
      infoMask == kCGImageAlphaNoneSkipFirst || 
      infoMask == kCGImageAlphaNoneSkipLast); 

    // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. 
    // https://developer.apple.com/library/mac/#qa/qa1037/_index.html 
    if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) { 
     // Unset the old alpha info. 
     bitmapInfo &= ~kCGBitmapAlphaInfoMask; 

     // Set noneSkipFirst. 
     bitmapInfo |= kCGImageAlphaNoneSkipFirst; 
    } 
      // Some PNGs tell us they have alpha but only 3 components. Odd. 
    else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) { 
     // Unset the old alpha info. 
     bitmapInfo &= ~kCGBitmapAlphaInfoMask; 
     bitmapInfo |= kCGImageAlphaPremultipliedFirst; 
    } 

    // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. 
    CGContextRef context = CGBitmapContextCreate(NULL, 
      imageSize.width, 
      imageSize.height, 
      CGImageGetBitsPerComponent(imageRef), 
      0, 
      colorSpace, 
      bitmapInfo); 
    CGColorSpaceRelease(colorSpace); 

    // If failed, return undecompressed image 
    if (!context) return image; 

    CGContextDrawImage(context, imageRect, imageRef); 
    CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 

    CGContextRelease(context); 

    UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; 
    CGImageRelease(decompressedImageRef); 
    return decompressedImage; 
} 

La mia domanda è dovremmo spostare l'approccio kCGImageSourceShouldCacheImmediately in iOS 7?

+1

Disclaimer: Sono l'autore di Haneke, un'alternativa a SDWebImage e sto chiedendo questo per informare come decodifico le immagini nella mia libreria. – hpique

+0

ciò che non è chiaro è qual è il problema che stai avendo che ha generato questa domanda? Perché sei preoccupato di questo? – SpaceDog

risposta

-2

Si potrebbe provare questo codice qui sotto:

+(NSData *)imageData:(UIImage *)image 
{ 
    //1.0 == 100% 
    return UIImageJPEGRepresentation(image, 0.7); 
} 

Cheers!

0

può essere in questo modo:

+ (UIImage *)imageFromURL:(NSURL *)url { 
    UIImage *result = nil; 
    if ([url isFileURL]) { 
     CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL); 
     if (source) { 
      NSDictionary * attributes = @{ (id)kCGImageSourceShouldCache : @YES }; 
      CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)attributes); 
      if (cgImage) { 
       result = [UIImage imageWithCGImage:cgImage]; 
       CGImageRelease(cgImage); 
      } 
      CFRelease(source); 
     } 
    } 
    return result; 
} 
3

Ci sono alcuni problemi con l'attuazione, per quanto posso dire.

  1. Questo "nuovo metodo" richiede un qualche tipo di rendere sul thread principale. È possibile caricare l'immagine e impostare immediatamente il flag di cache dovrebbe, ma questo imposterà alcune operazioni nel thread principale da elaborare. Ciò causerà quindi la balbuzie quando caricate le viste di scorrimento e le viste di raccolta. È più difficile per me che fare la vecchia maniera con le code di invio in background.

  2. Se si utilizzano i propri buffer di memoria invece dei file, è necessario creare provider di dati che copiano i dati, poiché sembra che i fornitori di dati si aspettino che i buffer di memoria si blocchino. Che suona ovvio, ma le bandiere in questo portare la funzione di credere che si può fare questo:

    • riempire il proprio buffer con i dati compressi JPEG da qualche fonte
    • creare un provider di dati e allegare i dati JPEG ad esso
    • creare l'origine dell'immagine con il fornitore di dati con CACHE ha immediatamente
    • utilizzare la sorgente di immagini per creare un'immagine CG
    • tiro tutti gli oggetti intermedi di distanza e consegnare il vostro ben decompresso CGImage oggetto fino ad un UIImage oggetto pronto per scorrimento

E non lo fa, però, perché sarà attendere che il thread principale in cui la decompressione avrà luogo. Pensa che tutto sia OK perché contiene riferimenti a tutti questi oggetti intermedi che hai rilasciato. Hai rilasciato tutti questi oggetti pensando che si sia decompresso IMMEDIATAMENTE come le bandiere hanno detto che lo farebbe. Se hai gettato via anche quel buffer di memoria, e quel buffer di memoria è stato passato in senso non cartaceo, finirai con la spazzatura.Oppure, se il buffer di memoria è stato riutilizzato come nel mio caso, per caricare un'altra immagine, si otterrà anche spazzatura.

In realtà non è possibile sapere quando questa immagine sarà decompressa e pronta per l'uso.

TL; DR = "considerare kCGImageSourceShouldCacheImmediately a dire quando conveniente per il sistema operativo"

Quando si esegue la "vecchia maniera", è 100% sa che cosa sta per essere a disposizione e quando. Poiché non è differito, puoi evitare di copiare. Non penso che questa API stia facendo qualcosa di magico in ogni caso, penso che sia solo tenere il buffer di memoria e poi fare le cose alla "vecchia maniera" sotto il cofano.

Quindi non c'è praticamente nessun pranzo gratis qui. Osservando la traccia dello stack da dove questa cosa si è schiantata quando ho ripreso a riutilizzare il mio buffer di memoria dopo che ho pensato che fosse tutto scollegato, lo vedo chiamare CA :: Transaction, CA :: Layer, CA :: Render e da lì in ImageProviderCopy ... fino in fondo a JPEGParseJPEGInfo (dove si è schiantato accedendo al mio buffer).

Ciò significa che kCGImageSourceShouldCacheImmediately non fa nulla tranne impostato un flag per raccontare l'immagine per decomprimere nel thread principale il più presto possibile dopo aver creato e in realtà non immediatamente come si pensa IMMEDIATAMENTE significa (in lettura). Avrebbe fatto la stessa identica cosa se hai passato l'immagine a una vista di scorrimento per visualizzare e l'immagine è andata a disegnare. Se sei fortunato ci sono stati alcuni cicli di riserva tra lo scrolling e questo migliorerebbe le cose, ma fondamentalmente penso che sia molto più speranzoso che farà più di quanto effettivamente non faccia.