Самый быстрый способ обработки сжатия UIImagePickerController - PullRequest
4 голосов
/ 01 октября 2009

Какой самый быстрый способ передачи изображения в хранилище данных SQLite для сжатия, чтобы я мог вернуть управление пользователю?

  • Я использую UIImagePickerController, чтобы делать снимки в моем приложении. Проблема использования картинки довольно медленная, из-за скорости UIImageJPEGRepresentation.
  • Я хочу перенести сжатие JPEG в фоновый поток, но прежде чем попытаться это сделать, мне нужно убедиться, что я могу сохранить изображение таким образом, чтобы оно сохранялось при разных прогонах. Это означает, что BLOB-объект в SQLite или файл. Что, насколько я могу судить, сразу возвращает меня к медленному кодированию изображений.

То, чего я хочу достичь, - это скорость, достаточная для того, чтобы пользователь чувствовал себя мгновенно.

Как мне справиться с этим? Есть что-нибудь еще, что я должен знать?

Ответы [ 3 ]

4 голосов
/ 03 октября 2009

Основываясь на комментариях и тестах, вот что я сейчас делаю:

Когда я получаю изображение из UIImageController, я сохраняю его в классе ivar и отключаю средство выбора изображений. Я показываю представление, которое блокирует мой основной вид, и планирую событие NSTimer, чтобы выполнить сжатие за секунду, а затем возвращаюсь к вызывающей стороне.

Это позволяет запустить анимацию, которая отключает контроллер изображения. Мой блокатор открывается под ним.

(Представление блокировщика заполняет всю область содержимого контроллера навигации и отображается сплошным черным цветом с 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];
}

Когда срабатывает таймер, я сжимаю изображение в формате JPEG (потому что он быстрее, чем PNG, несмотря на интуицию) и исчезаю из вида блокировщика.

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

Несмотря на то, что это добавляет секунду для обработки, оно убирает средство выбора изображений, поэтому мое приложение больше не чувствует себя замороженным. Анимация из UIActivityIndicatorView работает, пока работает UIImageJPEGRepresentation.

Лучшим ответом, чем использование NSTimer с задержкой в ​​1 секунду, было бы получение события после завершения анимации из dismissModalViewControllerAnimated:, но я не уверен, как это сделать.

(я пока не считаю это решенным.)

2 голосов
/ 01 октября 2009

Не следует сохранять изображение в базе данных, если только оно не очень маленького размера. Порог, который определяет, является ли картина достаточно маленьким, является, конечно, очень субъективным. По моему скромному мнению (и опыту на iPhone), он не должен превышать один мегабайт. Следовательно, вы должны сохранять в базе данных только изображения небольшого размера, такие как значки, эскизы и т. Д. Для изображений, размер которых превышает один мегабайт, вы должны просто сохранить их как файлы в файловой системе и указать имя файла (путь к изображению) в базе данных. Кстати, сохранение образа в файловой системе и его пути в базе данных происходит очень быстро.

О сжатии: вы, конечно, можете сжать изображение, используя другой поток, но подумайте, действительно ли это стоит делать. Вы можете использовать поток, чтобы сохранить изображение в файл, сохранить путь в базе данных и немедленно вернуть элемент управления вашему пользователю. У вас (как правило) достаточно места, но очень мало вычислительной мощности, даже на последнем iPhone 3GS. Кроме того, вы должны проверить (я действительно не знаю этого), требует ли загрузка сжатого изображения через UIImageView больше времени w.r.t. несжатый, такой как PNG. Если при загрузке сжатого изображения ваше приложение будет подвергаться дополнительным накладным расходам, то оно определенно может не стоит сжимать ваши изображения. Это в основном компромисс между пространством и скоростью. Надеюсь, это поможет решить.

0 голосов
/ 31 декабря 2012

Использование родительского, дочернего контекста управляемого объекта ios 5:

Контексты моего управляемого объекта расположены в следующем порядке:

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)

Модель выглядит так:

Image Entity -> title : string , image : relationship(ImageBlob) optional  
ImageBlob Entity -> image : Binary Data, imageEntity : relationship(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];
}
}

и вот как выполняется сохранение:

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

Это значительно уменьшает блокировку интерфейса пользователя на iphone 4, выбор 5-мегапиксельного изображения блокирует интерфейс только на 0,015 секунды.

с другой стороны, загрузка изображения также будет блокировать пользовательский интерфейс на заметное время, так что вы могли бы также загрузить его и в фоновом режиме.

...