0

I am designing an iOS application which displays a lot of images and videos to the user in a UICollectionView. I handle videos by generating a thumbnail of the video and displaying that, the video is only loaded when the user clicks on the thumbnail. Now I store the images and videos as base64 encoded blobs in my remote mysqli database. My question concerns the most efficient way of loading these images and videos back into the application. Currently, I use the following functions to generate thumbnails of the images as well as the video snapshots :

func RBSquareImageTo(image: UIImage, size: CGSize) -> UIImage? {
    return RBResizeImage(RBSquareImage(image), targetSize: size)
}

func RBSquareImage(image: UIImage) -> UIImage? {
    let originalWidth  = image.size.width
    let originalHeight = image.size.height

    var edge: CGFloat
    if originalWidth > originalHeight {
        edge = originalHeight
    } else {
        edge = originalWidth
    }

    let posX = (originalWidth  - edge) / 2.0
    let posY = (originalHeight - edge) / 2.0

    let cropSquare = CGRectMake(posX, posY, edge, edge)

    let imageRef = CGImageCreateWithImageInRect(image.CGImage, cropSquare);
    return UIImage(CGImage: imageRef!, scale: UIScreen.mainScreen().scale, orientation: image.imageOrientation)
}

func RBResizeImage(image: UIImage?, targetSize: CGSize) -> UIImage? {
    if let image = image {
        let size = image.size

        let widthRatio  = targetSize.width  / image.size.width
        let heightRatio = targetSize.height / image.size.height

        // Figure out what our orientation is, and use that to form the rectangle
        var newSize: CGSize
        if(widthRatio > heightRatio) {
            newSize = CGSizeMake(size.width * heightRatio, size.height * heightRatio)
        } else {
            newSize = CGSizeMake(size.width * widthRatio,  size.height * widthRatio)
        }

        // This is the rect that we've calculated out and this is what is actually used below
        let rect = CGRectMake(0, 0, newSize.width, newSize.height)

        // Actually do the resizing to the rect using the ImageContext stuff
        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
        image.drawInRect(rect)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage
    } else {
        return nil
    }
}
func videoSnapshot(vidURL: NSURL) -> UIImage? {

    let asset = AVURLAsset(URL: vidURL)
    let generator = AVAssetImageGenerator(asset: asset)
    generator.appliesPreferredTrackTransform = true

    let timestamp = CMTime(seconds: 1, preferredTimescale: 60)

    do {
        let imageRef = try generator.copyCGImageAtTime(timestamp, actualTime: nil)
        return UIImage(CGImage: imageRef)
    }
    catch let error as NSError
    {
        print("Image generation failed with error \(error)")
        return nil
    }
}

I then use the following functions to compress the content as well as the thumbnails and send them to my database in a POST request :

func compressVideo(inputURL: NSURL, outputURL: NSURL, handler:(session: AVAssetExportSession)-> Void)
{
    let urlAsset = AVURLAsset(URL: inputURL, options: nil)

    let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality)

    exportSession!.outputURL = outputURL

    exportSession!.outputFileType = AVFileTypeQuickTimeMovie

    exportSession!.shouldOptimizeForNetworkUse = true

    exportSession!.exportAsynchronouslyWithCompletionHandler { () -> Void in

        handler(session: exportSession!)
    }

}

func compressImage(image:UIImage) -> NSData {
    // Reducing file size to a 10th

    var actualHeight : CGFloat = image.size.height
    var actualWidth : CGFloat = image.size.width
    let maxHeight : CGFloat = 1136.0
    let maxWidth : CGFloat = 640.0
    var imgRatio : CGFloat = actualWidth/actualHeight
    let maxRatio : CGFloat = maxWidth/maxHeight
    var compressionQuality : CGFloat = 0.5

    if (actualHeight > maxHeight || actualWidth > maxWidth){
        if(imgRatio < maxRatio){
            //adjust width according to maxHeight
            imgRatio = maxHeight / actualHeight;
            actualWidth = imgRatio * actualWidth;
            actualHeight = maxHeight;
        }
        else if(imgRatio > maxRatio){
            //adjust height according to maxWidth
            imgRatio = maxWidth / actualWidth;
            actualHeight = imgRatio * actualHeight;
            actualWidth = maxWidth;
        }
        else{
            actualHeight = maxHeight;
            actualWidth = maxWidth;
            compressionQuality = 1;
        }
    }

    let rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
    UIGraphicsBeginImageContext(rect.size);
    image.drawInRect(rect)
    let img = UIGraphicsGetImageFromCurrentImageContext();
    let imageData = UIImageJPEGRepresentation(img, compressionQuality);
    UIGraphicsEndImageContext();

    return imageData!;
}

This results in the images and videos being between 500KB - 1.5MB and the thumbnails about 4-6 KB. I have tested this only with an ipad2, hence I suspect these sizes will increase significantly if a newer iPhone is used for taking the photos and videos. What I am currently doing is sending one request to my server, which returns the thumbnails of all the images and videos in the database, however the thumbnails are generally of low quality. I am wondering if there is a more efficient way of loading the full images and videos in this particular situation. I was thinking of doing something like sending a request which loads about 10 images only (the ones visible on screen) and when the user scrolls down, another request is sent for the next 10 images, the problem with this is, the cells will be blank until the request is complete if the user scrolls down fast enough. Is there any libraries you would recommend for this or maybe another approach which you would say is more efficient? Should I be using base64 encoding at all in this case?

Alk
  • 5,215
  • 8
  • 47
  • 116

1 Answers1

0

the cells will be blank until the request is complete if the user scrolls down fast enough

That's fine, as long as there is a smooth transition from a blank cell to one with a thumbnail. It occurs in popular apps like Facebook and Instagram too.

Should I be using base64 encoding at all in this case?

If you're concerned about efficiency, probably not. Generally, one would:

  • Assign the file(thumbnail/video) an identifier.
  • Store the identifier in the database and the file externally in the cloud, preferably with a CDN.
  • Client retrieves identifiers from the server, multiple at a time.
  • The client downloads the file from the external location and caches it.
Code
  • 6,041
  • 4
  • 35
  • 75
  • I would love to do something like storing the url of a file in my database and passing that in my json response and then simply having a function which downloads from that url. I currently use this in another section of my app where I store the url to fb profile pictures and download them this way. The problem is that these images are user generated content, not from facebook or anywhere like that, and I can't seem to find a valid service which would allow me to upload images and videos to it and give me a url which I can then use to access the image/video. – Alk Jun 12 '16 at 15:50
  • Would using a put request to store the file on the same server as my SQL database also work? Or what this not generate a URL which is accessible from the app – Alk Jun 12 '16 at 15:56
  • Yes it would work. But it may not be a good idea depending on your use case. See this topic http://stackoverflow.com/questions/2288402/should-i-persist-images-on-ebs-or-s3 – Code Jun 12 '16 at 16:04