处理 UIImagePickerController 压缩的最快方法

发布于 2024-08-06 07:18:31 字数 358 浏览 3 评论 0原文

将图片放入 SQLite 数据存储进行压缩以便我可以将控制权返回给用户的最快方法是什么?

  • 我正在使用 UIImagePickerController 在我的应用程序中拍照。问题是由于 UIImageJPEGRepresentation 的速度,使用图片的速度相当慢。
  • 我想将 JPEG 压缩推入后台线程,但在尝试此操作之前,我需要让自己确信我可以以跨运行的方式保留图片。这意味着 SQLite 中的 blob 或文件。据我所知,这让我立即回到进行慢速图片编码。

我想要实现的是速度足够快,让用户感觉是即时的。

我应该如何处理这个问题?还有什么我应该知道的吗?

What's the fastest way of getting a picture into a SQLite data store for compression, so I can return control to the user?

  • I'm using UIImagePickerController to take pictures in my application. The problem is using the picture is quite slow, because of the speed of UIImageJPEGRepresentation.
  • I want to push JPEG compression into a background thread, but before trying this I need to satisfy myself that I can persist the picture in a way that will survive across runs. That means either a blob in SQLite or a file. Which as far as I can tell, takes me right back to doing slow picture encoding right away.

What I want to achieve is speed fast enough that it feels instant to the user.

How should I be handling this? Is there anything else I should know?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浅浅 2024-08-13 07:18:31

根据评论和测试,这就是我当前正在做的事情:

当我从 UIImageController 获取图像时,我将其保留在类 ivar 中并关闭图像选择器。我显示了一个阻止主视图的视图,并安排一个 NSTimer 事件在一秒钟内进行压缩,然后返回给调用者。

这可以让动画运行并关闭图像控制器。我的拦截器视图显示在其下方。

(阻塞视图填充了导航控制器的整个内容区域,并且是纯黑色,带有 UIActivityIndi​​catorView。)

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

尽管这增加了第二次处理时间,但它可以更快地让图像选择器摆脱干扰,因此我不再感觉我的应用程序已冻结。 UIActivityIndi​​catorView 中的动画在 UIImageJPEGRepresentation 工作时运行。

比使用带有 1 秒延迟的 NSTimer 更好的答案是在来自 dismissModalViewControllerAnimated: 的动画完成时获取事件,但我不知道如何执行此操作。

(我认为这个问题还没有解决。)

Based on comments and tests, here's what I'm currently doing:

When I get the image from the UIImageController, I retain it in a class ivar and dismiss the image picker. I show a view that blocks my main view and schedule a NSTimer event to do the compression in a second, then return to the caller.

This lets the animation run that dismisses the image controller. My blocker view is revealed under it.

(The blocker view fills the entire content area of the navigation controller, and is solid black with a 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];
}

When the timer fires, I compress the image using JPEG (because it's faster than PNG, despite intuition) and fade away the blocker view.

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

Although this adds a second do the processing, it gets the image picker out of the way faster so it no longer feels like my application has frozen. The animation from the UIActivityIndicatorView does run while UIImageJPEGRepresentation is working.

A better answer than using the NSTimer with 1 second delay would be to get an event when the animation from dismissModalViewControllerAnimated: finishes, but I'm not sure how to do this.

(I don't consider this solved yet.)

女中豪杰 2024-08-13 07:18:31

您不应该将图片保存在数据库中,除非它的尺寸非常小。当然,决定图片是否足够小的阈值是非常主观的。以我的拙见(以及 iPhone 上的经验),它不应超过 1 MB。因此,您应该只在数据库中保存小尺寸的图像,例如图标、缩略图等。对于超过 1 MB 的图像,您应该将它们作为文件存储在文件系统中,并将文件名(图像路径)放入数据库中。顺便说一句,将图像存储在文件系统上并将其路径名存储在数据库中的速度非常快。

关于压缩:您当然可以使用另一个线程压缩图像,但请考虑是否真的值得这样做。您可以使用线程将图像保存到文件中,将路径名保存在数据库中并立即将控制权返回给用户。你(通常)有足够的空间,但计算能力却非常小,即使在最新的 iPhone 3GS 上也是如此。另外,您应该验证(我真的不知道这一点)通过 UIImageView 加载压缩图像是否需要比非压缩图像(例如 PNG)更多的时间。如果您的应用程序在加载压缩图像时会产生额外的开销,那么绝对不值得压缩图像。这基本上是空间和速度之间的权衡。希望这有助于做出决定。

You should not save a picture in the database, unless it's very small in size. The threshold which determines if the picture is small enough is, of course, highly subjective. In my humble opinion (and experience on the iPhone), it should not exceed one megabyte. Therefore, you should only save in the database small sized images, such as icons, thumbnails etc. For images beyond one megabytes you should simply store them as files in the filesystem, and put the filename (the image path) in the database. By the way, storing the image on the filesystem and its pathname in the database is extremely fast.

About compression: you can certainly compress the image using another thread, but consider whether or not it's really worth doing this. You can use a thread to save the image to a file, save the pathname in the database and return immediately the control to your user. You have (usually) plenty of space, but a very small computational power, even on the latest iPhone 3GS. Also, you should verify (I really do not know this) whether or not loading a compressed image through UIImageView requires more time w.r.t. a non compressed one such as a PNG. If your application will incur an additional overhead when loading a compressed image, then it may definitely not worth compressing your images. It's basically a tradeoff between space and speed. Hope this helps to decide.

巷子口的你 2024-08-13 07:18:31

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

这大大减少了 UI 上的阻塞,在 iPhone 4 上,选择 5 兆像素图像只会阻塞 UI 0.015 秒。

另一方面,加载图像也会在相当长的时间内阻塞 UI,因此您也可以在后台加载它。

Using ios 5's parent, child managed object context:

I have my managed object contexts arranged in this order:

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)

The model looks like :

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

relationship inverses are set.

once the user finishes picking an 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];
}
}

and this is how the saving is done:

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

This greatly reduces blockage on UI, on an iphone 4, choosing a 5 megapixel image will block the UI for only 0.015 seconds.

on the other hand, loading the image will block the UI for a noticeable time also, so you might've as well load it in background too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文