4

Quel est le moyen le plus rapide d'obtenir une image dans un magasin de données SQLite pour la compression, afin de pouvoir renvoyer le contrôle à l'utilisateur? J'utilise UIImagePickerController pour prendre des photos dans mon application. Le problème est l'utilisation de l'image est assez lente, à cause de la vitesse de UIImageJPEGRepresentation. La méthode la plus rapide pour gérer la compression UIImagePickerController

  • Je veux pousser la compression JPEG dans un thread d'arrière-plan, mais avant d'essayer cela, je dois me satisfaire que je peux persister l'image d'une manière qui survivra à travers les exécutions. Cela signifie soit un blob dans SQLite ou un fichier. Ce qui, pour autant que je sache, me ramène tout de suite à l'encodage lent de l'image. Ce que je veux atteindre, c'est une vitesse assez rapide pour que l'utilisateur se sente instantanément au courant.

    Comment dois-je gérer cela? Y a-t-il autre chose que je devrais savoir?

  • Répondre

    4

    Sur la base des commentaires et des tests, voici ce que je fais actuellement:

    Quand je reçois l'image du UIImageController, je retiens dans un Ivar de classe et congédie le sélecteur d'image. Je montre une vue qui bloque ma vue principale et planifie un événement NSTimer pour faire la compression en une seconde, puis retourner à l'appelant.

    Ceci permet à l'animation de s'exécuter qui rejette le contrôleur d'image. Ma vue de bloqueur est révélée sous elle.

    (La vue bloqueur remplit toute la zone contenu du contrôleur de navigation, et est solide noir avec un UIActivityIndicatorView.)

    - (void)imagePickerController: (UIImagePickerController *)picker 
         didFinishPickingImage: (UIImage *)selectedImage 
            editingInfo: (NSDictionary *)editingInfo; 
    { 
        busyView.userInteractionEnabled = YES; 
        busyView.alpha = 0.7f; 
        mainView.userInteractionEnabled = NO; 
        [self dismissModalViewControllerAnimated: YES]; 
        [NSTimer scheduledTimerWithTimeInterval: 1.0f 
                target: self 
                selector: @selector(compress:) 
                userInfo: selectedImage 
                repeats: NO]; 
    } 
    

    Lorsque les feux de la minuterie, je compriment l'image en JPEG (car il est plus rapide que PNG, malgré l'intuition) et s'estomper la vue du bloqueur.

    - (void)compress: (NSTimer *)inTimer; 
    { 
        [self gotJPEG: UIImageJPEGRepresentation(inTimer.userInfo, 0.5f)]; 
        [UIView beginAnimations: @"PostCompressFade" context: nil]; 
        [UIView setAnimationDuration: 0.5]; 
        busyView.userInteractionEnabled = NO; 
        busyView.alpha = 0.0f; 
        [UIView commitAnimations]; 
        mainView.userInteractionEnabled = YES; 
    } 
    

    Bien que cela ajoute une seconde effectuer le traitement, il obtient le sélecteur d'image de la façon plus rapide il ne se sent plus comme ma demande a gelé. L'animation du UIActivityIndicatorView s'exécute pendant que UIImageJPEGRepresentation fonctionne.

    Une meilleure réponse que d'utiliser le NSTimer avec 1 seconde de retard serait d'obtenir un événement lorsque l'animation de dismissModalViewControllerAnimated: se termine, mais je ne suis pas sûr de savoir comment faire.

    (. Je ne considère pas encore résolu)

    +2

    Au lieu d'utiliser le temporisateur, faites-le dans la méthode viewDidAppear de la vue affichée après le retrait du sélecteur. – Shizam

    +2

    A partir d'iOS 5, vous pouvez utiliser le bloc d'achèvement de - (void) dismissViewControllerAnimated: (BOOL) flag completion: (void (^) (void)) achèvement; pour archiver le même. – Klaas

    +0

    J'ai récemment utilisé 'dispatch_async' récemment pour cela. Je devrais probablement mettre à jour ma réponse. –

    2

    Vous ne devez pas enregistrer une image dans la base de données, sauf si elle est de très petite taille. Le seuil qui détermine si l'image est suffisamment petite est, bien sûr, hautement subjectif. À mon humble avis (et expérience sur l'iPhone), il ne devrait pas dépasser un mégaoctet. Pour les images au-delà d'un mégaoctet, vous devez simplement les stocker sous forme de fichiers dans le système de fichiers et placer le nom de fichier (le chemin de l'image) dans la base de données. En passant, stocker l'image sur le système de fichiers et son chemin d'accès dans la base de données est extrêmement rapide. A propos de la compression: vous pouvez certainement compresser l'image en utilisant un autre thread, mais réfléchissez si cela vaut vraiment la peine. Vous pouvez utiliser un thread pour enregistrer l'image dans un fichier, enregistrer le chemin d'accès dans la base de données et renvoyer immédiatement le contrôle à votre utilisateur. Vous avez (généralement) beaucoup d'espace, mais une très petite puissance de calcul, même sur le dernier iPhone 3GS. Aussi, vous devriez vérifier (je ne sais vraiment pas) si le chargement d'une image compressée via UIImageView nécessite plus de temps. un non compressé tel qu'un PNG. Si votre application subit un surcoût supplémentaire lors du chargement d'une image compressée, il ne vaut certainement pas la peine de compresser vos images. C'est fondamentalement un compromis entre l'espace et la vitesse. J'espère que cela aide à décider.

    +0

    Comme l'image va être téléchargée, cela devient un compromis entre l'espace, la vitesse et la bande passante ... plus si je ne compresse pas la photo, j'aurai besoin du gars du serveur pour pouvoir interpréter l'image sur le serveur. Nous avons effectué des tests sur la vitesse du blob SQLite dans le passé, donc je ne m'inquiète pas trop du choix du fichier par rapport au blob. Bien que je vais probablement les exécuter à nouveau. Merci pour la réponse utile. A partir de là, je suppose que je devrais chercher à extraire des données non compressées de UIImagePicker? –

    +0

    Vous devrez utiliser des méthodes de représentation PNG ou JPG afin d'obtenir NSData vous pouvez écrire dans un fichier de toute façon. Vous pourriez tester si PNG est plus rapide car la compression est moindre, et il y a une chance que la représentation interne soit PNG de toute façon ... –

    +0

    Comme le souligne Kendall, je pense que PNG sera plus rapide. Essaie. –

    0

    utilisant ios parent de 5, enfant géré contexte de l'objet:

    J'ai mes contextes d'objets gérés disposés dans cet ordre:

    persistent store coordinator ---> 
    Private Queue Managed Object Context (for saving to disk in background) -----> 
    Main Queue Managed Object Context (for UI) -----> 
    Misc. Private Managed Object Contexts (for temporary jobs like UIImagePNGRepresentation() for example) 
    

    Le modèle ressemble à:

    Image Entity -> title : string , image : relationship(ImageBlob) optional 
    ImageBlob Entity -> image : Binary Data, imageEntity : relationship(Image) 
    

    Les relations inverses sont définies.

    une fois que l'utilisateur se termine en choisissant une image:

    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 
    { 
    // get the main queue managed object context 
    NSManagedObjectContext* mainQueueManagedObjectContext = self.managedObjectContext; 
    
    // get the image 
    UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage]; 
    
    // create an object, using the managed object context for the main queue 
    NSManagedObject *newImage = [NSEntityDescription insertNewObjectForEntityForName:@"Image" inManagedObjectContext:mainQueueManagedObjectContext]; 
    
    // edit not expensive properties 
    [newImage setValue:[NSString stringWithFormat:@"new title %i", [self tableView:self.tableView numberOfRowsInSection:0]] forKey:@"title"]; 
    
    // lets save the main context to get a permanant objectID 
    [self saveContextForManagedObjectContext:mainQueueManagedObjectContext]; 
    
    // get the permenant objectID, Thread Safe.. 
    NSManagedObjectID* imageObjectID = newImage.objectID; 
    
    // create a private queue concurrent managed object context 
    NSManagedObjectContext* privateQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    
    // set the main queue as the parent 
    [privateQueueManagedObjectContext setParentContext:mainQueueManagedObjectContext]; 
    
    // we have to use blocks here, as this managed object context will work in a private queue 
    [privateQueueManagedObjectContext performBlock: 
    ^{ 
        // get the png representation in background 
        NSData* data = UIImagePNGRepresentation(image); 
    
        // get the managed object using the thread safe objectID 
        NSManagedObject* imageObjectInPrivateQueue = [privateQueueManagedObjectContext objectWithID:imageObjectID]; 
    
        // insert a new object for the ImageBlob entity 
        NSManagedObject *imageBlobInPrivateQueue = [NSEntityDescription insertNewObjectForEntityForName:@"ImageBlob" inManagedObjectContext:privateQueueManagedObjectContext]; 
    
        // set our image data 
        [imageBlobInPrivateQueue setValue:data forKey:@"image"]; 
    
        // set the relationship to the original record 
        [imageObjectInPrivateQueue setValue:imageBlobInPrivateQueue forKey:@"image"]; 
    
        // save changes to private queue context to main queue context 
        [self saveContextForManagedObjectContext:privateQueueManagedObjectContext]; 
    
        // since we are not in the main queue, we have to ask the main managed object context using performBlock 
        [mainQueueManagedObjectContext performBlock: 
         ^{ 
          // what time is it before launching save in main queue 
          NSDate* startDate = [NSDate date]; 
    
          // launch save on main queue 
          [self saveContextForManagedObjectContext:mainQueueManagedObjectContext]; 
    
          // what time is it after finishing save in main queue 
          NSDate* finishDate = [NSDate date]; 
    
          // see how long UI blocked 
          NSLog(@"blocked UI for %f seconds", [finishDate timeIntervalSinceDate:startDate]); 
         }]; 
    
    }]; 
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 
    { 
        [self.popOverController dismissPopoverAnimated:YES]; 
    } 
    else 
    { 
        [self dismissViewControllerAnimated:YES completion:nil]; 
    } 
    } 
    

    et voilà comment l'économie se fait:

    -(void)saveContextForManagedObjectContext:(NSManagedObjectContext*)managedObjectContext 
    { 
    // Save the context. 
    NSError *error = nil; 
    if (![managedObjectContext save:&error]) { 
        // Replace this implementation with code to handle the error appropriately. 
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
        abort(); 
    } 
    } 
    

    Cela réduit considérablement le blocage sur l'interface utilisateur, sur un iPhone 4, le choix d'un 5 image mégapixel va bloquer l'interface utilisateur pour seulement 0,015 secondes. D'un autre côté, le chargement de l'image bloquera également l'interface utilisateur pendant une période de temps notable. Vous pouvez donc également la charger en arrière-plan.

    Questions connexes