4

I have an app that lets people combine up to 4 pictures. However when I let them choose from their photos (up to 4) it can be very slow even when I set image quality to FastFormat. It will take 4 seconds (about 1 second per photo). On highest quality, 4 images takes 6 seconds.

Can you suggest anyway I get get the images out faster?

Here is the block where I process images.

func processImages()
    {
        _selectediImages = Array()
        _cacheImageComplete = 0
        for asset in _selectedAssets
        {
            var options:PHImageRequestOptions = PHImageRequestOptions()
            options.synchronous = true
            options.deliveryMode = PHImageRequestOptionsDeliveryMode.FastFormat
            PHImageManager.defaultManager().requestImageForAsset(asset, targetSize:CGSizeMake(CGFloat(asset.pixelWidth), CGFloat(asset.pixelHeight)), contentMode: .AspectFit, options: options)
                {
                    result, info in
                    var minRatio:CGFloat = 1
                    //Reduce file size so take 1/3 the screen w&h
                    if(CGFloat(asset.pixelWidth) > UIScreen.mainScreen().bounds.width/2 || CGFloat(asset.pixelHeight) > UIScreen.mainScreen().bounds.height/2)
                    {
                        minRatio = min((UIScreen.mainScreen().bounds.width/2)/(CGFloat(asset.pixelWidth)), ((UIScreen.mainScreen().bounds.height/2)/CGFloat(asset.pixelHeight)))
                    }
                    var size:CGSize = CGSizeMake((CGFloat(asset.pixelWidth)*minRatio),(CGFloat(asset.pixelHeight)*minRatio))
                    UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
                    result.drawInRect(CGRectMake(0, 0, size.width, size.height))
                    var final = UIGraphicsGetImageFromCurrentImageContext()
                    var image = iImage(uiimage: final)
                    self._selectediImages.append(image)
                    self._cacheImageComplete!++
                    println(self._cacheImageComplete)
                    if(self._cacheImageComplete == self._selectionCount)
                    {
                        self._processingImages = false
                        self.selectionCallback(self._selectediImages)
                    }
            }

        }
    }
Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • This may be help to achieve what you need without much more efforts. Take a look at iOS code no need to run React Native code : https://stackoverflow.com/questions/49305438/how-to-get-thumb-image-from-photolibrary-image – Nilesh Kikani Jun 28 '18 at 11:02

2 Answers2

7

Don't resize the images yourself — part of what PHImageManager is for is to do that for you. (It also caches the thumbnail images so that you can get them more quickly next time, and shares that cache across apps so that you don't end up with half a dozen apps creating half a dozen separate 500MB thumbnail caches of your whole library.)

func processImages() {
    _selectediImages = Array()
    _cacheImageComplete = 0
    for asset in _selectedAssets {
        let options = PHImageRequestOptions()
        options.deliveryMode = .FastFormat

        // request images no bigger than 1/3 the screen width
        let maxDimension = UIScreen.mainScreen().bounds.width / 3 * UIScreen.mainScreen().scale
        let size = CGSize(width: maxDimension, height: maxDimension)

        PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: size, contentMode: .AspectFill, options: options)
            { result, info in
                // probably some of this code is unnecessary, too,
                // but I'm not sure what you're doing here so leaving it alone
                self._selectediImages.append(result)
                self._cacheImageComplete!++
                println(self._cacheImageComplete)
                if self._cacheImageComplete == self._selectionCount {
                    self._processingImages = false
                    self.selectionCallback(self._selectediImages)
                }
            }
        }
    }
}

Notable changes:

  • Don't ask for images synchronously on the main thread. Just don't.
  • Pass a square maximum size to requestImageForAsset and use the AspectFill mode. This will get you an image that crops to fill that square no matter what its aspect ratio is.
  • You're asking for images by their pixel size here, and the screen size is in points. Multiply by the screen scale or your images will be pixelated. (Then again, you're asking for FastFormat, so you might get blurry images anyway.)
rickster
  • 124,678
  • 26
  • 272
  • 326
  • Great thank you for the extra clarification. I am going to go through my whole app on the weekend and change everything to scaled pixels. I will try this right now – Aggressor Dec 10 '14 at 19:09
6

Why did you say synchronous? Obviously that's going to slow things way down. Moreover, saying synchronous on the main thread is absolutely forbidden!!!! Read the docs and obey them. That is the primary issue here.

There are then many other considerations. Basically you're using this call all wrong. Once you've removed the synchronous, do not process the image like that! Remember, this callback is going to be called many times as the image is provided in better and better versions. You must not do anything time-consuming here.

(Also, why are you resizing the image? If you wanted the image at a certain size, you should have asked for that size when you requested it. Let the image-fetcher do the work for you.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I just started doing an aysnc call. But regarding the image size, the issue was this:http://stackoverflow.com/questions/26660534/how-to-properly-compress-uiimages-at-runtime – Aggressor Dec 10 '14 at 18:49
  • Do you know a better way I get get smaller images? – Aggressor Dec 10 '14 at 18:49
  • Pls don't leech. I've explained the cause of the slowness: you're saying `synchronous` on the main thread. That was what you asked. If you have another question, ask another question. – matt Dec 10 '14 at 18:51
  • Could you answer the one I posted above? – Aggressor Dec 10 '14 at 18:51
  • Yes but what you suggest here is that I'm not resizing it properly (and the code above is the result of that answer) and I'd be happy to switch the answer to yours if you can clarify at what point I should re-size it with the image-fetcher so there is less load burden – Aggressor Dec 10 '14 at 18:57
  • 1
    What was wrong with the answer I gave _here_? The image-fetcher lets you specify a reasonable size for display. Remember, your UI is just your UI. You don't need to work with the real image until / unless the user approves an edit on a photo. – matt Dec 10 '14 at 19:04
  • Gotcha thanks Ill keep tweaking it till I get it right. Thanks – Aggressor Dec 10 '14 at 19:12
  • @matt if I do not say options.synchronous = true then completion is called twice and mage is appended twice in array. Any suggestion please? – Jagdeep Singh Sep 04 '18 at 05:48
  • @JagdeepSingh That's correct behavior. You get an intermediate lesser quality image (possibly more than one) before you get the final image, unless your fetch options specifically say not to do that. – matt Sep 04 '18 at 06:54