Самый быстрый способ обработки сжатия UIImagePickerController


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

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

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

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

3 4

3 ответа:

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

Когда я получаю изображение из 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: заканчивается, но я не уверен, как это сделать.

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

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

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

Использование контекста родительского, дочернего управляемого объекта 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 секунды.

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