Downsampling Images - swift

My Image loading code looks like this:
final class ImageStore {
typealias _ImageDictionary = [String: CGImage]
fileprivate var images: _ImageDictionary = [:]
fileprivate static var scale = 2
static var shared = ImageStore()
func image(name: String) -> Image {
let index = _guaranteeImage(name: name)
return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(verbatim: name))
}
static func loadImage(name: String) -> CGImage {
guard
let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else {
fatalError("Couldn't load image \(name).jpg from main bundle.")
}
return image
}
fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
if let index = images.index(forKey: name) { return index }
images[name] = ImageStore.loadImage(name: name)
return images.index(forKey: name)!
}
}
I have 300 images and their average size is 500 kb(3000px X 3000px).
I show them with LazyVGrid. After load approximately 250 images, memory use reach up to 1GB and app crash.
I found that every pixel occupies 4 byte(1 for red, 1 for green, 1 for blue, 1 for alpha) in RAM and this results 36 MB Memory footprint for each image. (3000x3000x4). See here.
Above link suggest that downsampling could help to reduce Memory footprint.
Here is the downsampling code:
func downsample(imageAt imageURL: URL,
to pointSize: CGSize,
scale: CGFloat = UIScreen.main.scale) -> UIImage? {
// Create an CGImageSource that represent an image
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else {
return nil
}
// Calculate the desired dimension
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
// Perform downsampling
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
// Return the downsampled image as UIImage
return UIImage(cgImage: downsampledImage)
}
I merged above 2 codes with like this:
final class ImageStore {
typealias _ImageDictionary = [String: CGImage]
fileprivate var images: _ImageDictionary = [:]
fileprivate static var scale = 2
static var shared = ImageStore()
// Perform downsampling
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: 950
] as CFDictionary //Added this block
func image(name: String) -> Image {
let index = _guaranteeImage(name: name)
return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(verbatim: name))
}
static func loadImage(name: String) -> CGImage {
guard
let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) //Added this line
else {
fatalError("Couldn't load image \(name).jpg from main bundle.")
}
return image
}
fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
if let index = images.index(forKey: name) { return index }
images[name] = ImageStore.loadImage(name: name)
return images.index(forKey: name)!
}
}
But I get this error:
Instance member 'downsampleOptions' cannot be used on type 'ImageStore'
How can I downsample the images?(In other words, I need thumbnails for the image gallery)

Related

Get Image resolution from an URL in the background thread

I'm trying to get the resolution of an image from URL without actually downloading it. So I have a function for that:
public func resolutionForImage(url: String) -> CGSize? {
guard let url = URL(string: url) else { return nil }
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else {
return nil
}
let propertiesOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, propertiesOptions) as? [CFString: Any] else {
return nil
}
if let width = properties[kCGImagePropertyPixelWidth] as? CGFloat,
let height = properties[kCGImagePropertyPixelHeight] as? CGFloat {
return CGSize(width: width, height: height)
} else {
return nil
}
}
It works fine, but I need to run it in the Background thread, in the main thread in block the UI
And this function is called in an another function in a collectionView cell, so in the end calculateImageHeight output should be in the main thread, could anyone help me manage it, still not in good in the threading
public func calculateImageHeight(ratio: Double = 0.0) -> CGFloat {
var calculatedRatio = ratio
if ratio == 0.0 {
if let size = resolutionForImage(url: imageUrl) {
calculatedRatio = size.height/size.width
}
}
let height = imageViewWidth * calculatedRatio
return height.isFinite ? height : 100
}

how to show an image from URL from AFnetworking with Swift?

This would be a super noob question but I have no idea how to convert URL to data and data to UIimage. The text labels are work really beautifully but the UI images are not working
I figured out there are some codes to convert them but don't know how to use them. could you help me out.
override func viewDidLoad() {
super.viewDidLoad()
let manager = AFHTTPSessionManager()
let url = mainURL + "sample.php"
manager.get(url, parameters: nil, progress: nil, success: { (task, res) in
guard let json = res as? [String: Any] else {
print ("not [String: Any]]")
return
}
if let array = json["data"] as? [Any] {
for i in 0 ..< array.count {
if let row = array [ i ] as? [String: Any] {
let model = Model(t: row ["title"] as! String,
n: row ["user"] as! String,
d: row ["regdate"] as! String,
imgUrl: row ["img"] as! String)
self.models.append(model)
}
self.tableView.reloadData()
}
}
}) { (task, error) in
print("error = \(error)")
}
self.tableView.estimatedRowHeight = 500
}
Model
class Model : NSObject {
//?
let data = try? Data(contetsOf: url!)
let img = UIImage(data: data!)
let imgView = UIImageView(image: img)
var title : String
var name : String
var date : String
var image : UIImage!
init(t: String, n: String, d: String, imgUrl: String){
title = t
name = n
date = d
image = UIImage(named: imgUrl)
}
and the outlets.
mainCell.titleLabel.text = m.title //labels work fine
mainCell.nameLabel.text = m.name
mainCell.mainImgView.image = m.image
Try this
Just store image url in model class not image. Update your model class with below code
class Model : NSObject {
var title : String
var name : String
var date : String
var image : String
init(t: String, n: String, d: String, imgUrl: String){
title = t
name = n
date = d
image = imgUrl
}
}
// past code inside cellforRowAt
// download image from image URL
let url = URL(string: image)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
mainCell.mainImgView.image = UIImage(data: data!)
}
}).resume()

swift generate a qrcode [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 2 years ago.
I tried to generate a qrcode, but it has error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
let myString = "ggigiuui"
let data = myString.data(using: .ascii, allowLossyConversion: false)
let filter = CIFilter(name: "CIQRCodeGenerator")
filter?.setValue(data, forKey: "inputMessage")
let img = UIImage(ciImage: (filter?.outputImage)!)
qponImage.image = img
I have used the following code, and it is working perfectly.Where self.imgQRCode is the imageview on which you want to display QR.
func generateQRCode(from string: String) -> UIImage?
{
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CIQRCodeGenerator")
{
filter.setValue(data, forKey: "inputMessage")
guard let qrImage = filter.outputImage else {return nil}
let scaleX = self.imgQRCode.frame.size.width / qrImage.extent.size.width
let scaleY = self.imgQRCode.frame.size.height / qrImage.extent.size.height
let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
if let output = filter.outputImage?.transformed(by: transform)
{
return UIImage(ciImage: output)
}
}
return nil
}
Please try this,
func generateQRCode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 3, y: 3)
if let output = filter.outputImage?.transformed(by: transform) {
return UIImage(ciImage: output)
}
}
return nil
}
This is how you can generate a QRCode and display in UIImageView
first of all create new Cocoa Touch Class .swift file and import these two framework:
import UIKit
import CoreImage
and the second step you just need to add the extension of URL and CIImage on the same .swift file.
extensions :
extension URL {
/// Creates a QR code for the current URL in the given color.
func qrImage(using color: UIColor, logo: UIImage? = nil) -> CIImage? {
let tintedQRImage = qrImage?.tinted(using: color)
guard let logo = logo?.cgImage else {
return tintedQRImage
}
return tintedQRImage?.combined(with: CIImage(cgImage: logo))
}
/// Returns a black and white QR code for this URL.
var qrImage: CIImage? {
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
let qrData = absoluteString.data(using: String.Encoding.ascii)
qrFilter.setValue(qrData, forKey: "inputMessage")
let qrTransform = CGAffineTransform(scaleX: 12, y: 12)
return qrFilter.outputImage?.transformed(by: qrTransform)
}
}
extension CIImage {
/// Inverts the colors and creates a transparent image by converting the mask to alpha.
/// Input image should be black and white.
var transparent: CIImage? {
return inverted?.blackTransparent
}
/// Inverts the colors.
var inverted: CIImage? {
guard let invertedColorFilter = CIFilter(name: "CIColorInvert") else { return nil }
invertedColorFilter.setValue(self, forKey: "inputImage")
return invertedColorFilter.outputImage
}
/// Converts all black to transparent.
var blackTransparent: CIImage? {
guard let blackTransparentFilter = CIFilter(name: "CIMaskToAlpha") else { return nil }
blackTransparentFilter.setValue(self, forKey: "inputImage")
return blackTransparentFilter.outputImage
}
/// Applies the given color as a tint color.
func tinted(using color: UIColor) -> CIImage?
{
guard
let transparentQRImage = transparent,
let filter = CIFilter(name: "CIMultiplyCompositing"),
let colorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil }
let ciColor = CIColor(color: color)
colorFilter.setValue(ciColor, forKey: kCIInputColorKey)
let colorImage = colorFilter.outputImage
filter.setValue(colorImage, forKey: kCIInputImageKey)
filter.setValue(transparentQRImage, forKey: kCIInputBackgroundImageKey)
return filter.outputImage!
}
/// Combines the current image with the given image centered.
func combined(with image: CIImage) -> CIImage? {
guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
let centerTransform = CGAffineTransform(translationX: extent.midX - (image.extent.size.width / 2), y: extent.midY - (image.extent.size.height / 2))
combinedFilter.setValue(image.transformed(by: centerTransform), forKey: "inputImage")
combinedFilter.setValue(self, forKey: "inputBackgroundImage")
return combinedFilter.outputImage!
}
}
and the third step you have to bund the outlet of your imageview in which you want to display generated QRCode.
your ViewController.swift file something like this.
// desired color of QRCode
let OrangeColor = UIColor(red:0.93, green:0.31, blue:0.23, alpha:1.00)
// app logo or whatever UIImage you want to set in the center.
let Logo = UIImage(named: "logo_which_you_want_to_set_in_the center_of_the_QRCode")!
#IBOutlet weak var imgQRImage: UIImageView!
and last and final step add the QRCode to imgQRImage and put the code in your viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
let QRLink = "https://www.peerbits.com/"
guard let qrURLImage = URL(string: QRLink)?.qrImage(using: self.OrangeColor, logo: self.Logo)else{return}
self.imgQRImage.image = UIImage(ciImage: qrURLImage)
}
As mention in docs we can use CIQRCodeGenerator
func qrCode(_ outputSize: CGSize) -> UIImage?
{
if let data = data(using: .isoLatin1),
let outputImage = CIFilter(
name: "CIQRCodeGenerator",
parameters: [
"inputMessage": data,
"inputCorrectionLevel": "Q"
]
)?.outputImage {
let size: CGRect = outputImage.extent.integral
let format = UIGraphicsImageRendererFormat()
format.scale = UIScreen.main.scale
return UIGraphicsImageRenderer(size: output, format: format)
.image { _ in
outputImage
.transformed(
by: .init(
scaleX: outputSize.width/size.width,
y: outputSize.height/size.height
)
)
.uiimage
.draw(in: .init(origin: .zero, size: outputSize))
}
} else {
return nil
}
}
extension CIImage {
var uiimage: UIImage {
.init(ciImage: self)
}
}
this is bit modified version of this post
and in case u need to parse qr code image for content
func decodeQRCode(_ image: UIImage?) -> [CIQRCodeFeature]? {
if let image = image,
let ciImage = CIImage(image: image) {
let context = CIContext()
var options: [String: Any] = [
CIDetectorAccuracy: CIDetectorAccuracyHigh
]
let qrDetector = CIDetector(
ofType: CIDetectorTypeQRCode,
context: context,
options: options
)
if ciImage.properties.keys
.contains((kCGImagePropertyOrientation as String)) {
options = [
CIDetectorImageOrientation: ciImage
.properties[(kCGImagePropertyOrientation as String)] as Any
]
} else {
options = [CIDetectorImageOrientation: 1]
}
let features = qrDetector?.features(in: ciImage, options: options)
return features?
.compactMap({ $0 as? CIQRCodeFeature })
}
return nil
}
}

How to prevent tableView from crashing with asynchronous methods?

I am making a tableView with pictures inside it. The pictures are the profile pictures from people which can join, leave and make there own room. In that room, the tableView is displayed.
To save data, I want to store every profile picture in the documents directory when people are joining the room. Now that is working, but the tableView reloads to often even when I am calling it twice. Because it reloads to much, it crashes because the array is out of index.
The crash happens to be on the picture array, not the username array. This is my code (a bit much):
private var playersRefHandle: FIRDatabaseHandle?
var channelRef: FIRDatabaseReference?
var players = [String]()
var playerImages = [UIImage]()
var playerUIDs = [String]()
var playersImageVersion = [String]()
var channel: Channel? {
didSet {
title = channel?.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
playersView.delegate = self
playersView.dataSource = self
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: "gs://X-f5beb.appspot.com")
channelRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let UIDs = each.value["userID"] as? String
let pictureVersion = each.value["PictureVersion"] as? String
if let allUIDS = UIDs{
if let allPictureVersions = pictureVersion{
self.playerUIDs.append(UIDs!)
self.playersImageVersion.append(allPictureVersions)
let userNames = each.value["username"] as? String
if let users = userNames{
self.players.append(users)
}
if self.checkDataExist(dataToCheck: "\(UIDs!)" + "Image.png") == false || self.isCurrentImageVersionStored(pathToImage: "\(UIDs!)" + "ImageVersion.txt", playersVersionImage: "\(allPictureVersions)") == false
{
print("image needs to be downloaded online")
let profilePicRef = storageRef.child((allUIDS)+"/profile_picture.png")
profilePicRef.data(withMaxSize: 1 * 500 * 500) { data, error in
if let error = error {
}
if (data != nil)
{
let image = UIImage(data: data!)
let convertImage = UIImagePNGRepresentation(image!)
let pathUIDImage = self.getDocumentsDirectory().appendingPathComponent(allUIDS + "Image.png")
try? convertImage!.write(to: pathUIDImage)
let playersUIDImageVersion = allPictureVersions
var pathUIDImageVersion = self.getDocumentsDirectory().appendingPathComponent(allUIDS + "ImageVersion.txt")
try? playersUIDImageVersion.write(to: pathUIDImageVersion, atomically: true, encoding: .utf8)
self.playerImages.append(UIImage (data: data!)!)
}
}
}
else
{
self.playerImages.append(self.retrieveImageFromDocuments(playersUID: UIDs!))
}
}
}
}
}
self.playersView.reloadData()
self.observePlayers()
})
}
deinit {
if let refHandle = playersRefHandle {
channelRef?.removeObserver(withHandle: refHandle)
}
}
override func willMove(toParentViewController parent: UIViewController?)
{
if parent == nil
{
print("back")
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func checkDataExist(dataToCheck: String) -> Bool
{
let documentsURL = try! FileManager().url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
let file = documentsURL.appendingPathComponent(dataToCheck)
let fileExists = FileManager().fileExists(atPath: file.path)
if fileExists == true
{
return true
}
else
{
return false
}
}
func isCurrentImageVersionStored(pathToImage: String, playersVersionImage: String) -> Bool
{
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(pathToImage)
do {
let currentStoredImage = try String(contentsOf: path, encoding: String.Encoding.utf8)
let currentStoredImageInt = Int(currentStoredImage)
let playersVersionImageInt = Int(playersVersionImage)
if currentStoredImageInt == playersVersionImageInt
{
return true
}
else
{
return false
}
}
catch {/* error handling here */}
return false
}
return false
}
func retrieveImageFromDocuments(playersUID: String) -> UIImage
{
print("image is available offline")
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
let dirPath = paths.first
let imageURL = URL(fileURLWithPath: dirPath!).appendingPathComponent("\(playersUID)" + "Image.png")
let profileImageForUser = UIImage(contentsOfFile: imageURL.path)
return profileImageForUser!
}
func observePlayers()
{
playersRefHandle = channelRef?.child("username").observe(.childChanged, with: { (snapshot) -> Void in
print("added player")
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("configuring one of multiple cells")
print(playerImages.count)
print(players.count)
var cell = tableView.dequeueReusableCell(withIdentifier: "playersCell") as! PlayersCellInMultiplayer
cell.playersUsername?.text = players[indexPath.row] as String
cell.playersImage?.image = playerImages[indexPath.row] as UIImage
return cell
}
This is my print of a random channel with a few users in it:
image is available offline
image needs to be downloaded online
configuring one of multiple cells
1
2
configuring one of multiple cells
1
2
Why does this happen? Is this of asynchronous methods? What is the best way to fix it?

download and save a PFFile (UIImage) to show in a UIImageView : SWIFT

I have a large class called "Goal" in parse. This class has multiple elements, one of which is a PFFile, that is always a UIImage.
When I perform my query for the "Goal" class, I cannot figure out how to take the PFFile, and change it to a UIImage for use.
var query = PFQuery(className:"Goal")
let currentUser = PFUser.currentUser()!.username
query.whereKey("creator", equalTo: currentUser!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects?.count) goals for the TableView.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
let goalType = object["type"] as! String
let goalPeriod = object["period"] as! String
let goalCategory = object["category"] as! String
let goalShortDescription = object["shortDescription"] as! String
let goalLongDescription = object["longDescription"] as! String
let goalPointvalue = object["pointValue"] as! Int
let goalSharedSetting = object["shared"] as! Bool
let goalAdoptionCount = object["adoptionCount"] as! Int
let goalIsComplete = object["isComplete"] as! Bool
let goalSuccessImageData = object["image"] as! PFFile
goalSuccessImageData.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
self.imageQuery = image
}
}
}
let goalSuccessImage : UIImage = self.imageQuery
let goalObjectID = object.objectId
let goalSpreadCount = object["spreadCount"] as! Int
let goalSpreadTotal = object["spreadTotal"] as! Int
let goalTotalCompletions = object["totalCompletions"] as! Int
let thisGoal = GoalModel(period: goalPeriod, type: goalType, category: goalCategory, shortDescription: goalShortDescription, longDescription: goalLongDescription, pointValue: goalPointvalue, shared: goalSharedSetting, adoptionCount: goalAdoptionCount, isComplete: goalIsComplete, successImage: goalSuccessImage, goalID: goalObjectID!, spreadCount: goalSpreadCount, spreadTotal: goalSpreadTotal, totalCompletions: goalTotalCompletions ) as GoalModel
any tips on how to modify the "success image" part? I added a space before and after to make it easier to find.
Thank you in advance!
I'm using this way in my projects, if it help's you :
func performSave(sender: UIBarButtonItem){
affichageActivityIndicator()
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos,0)) { () -> Void in
dispatch_async(dispatch_get_main_queue()){
if let updateObject = self.currentObject as PFObject? {
let imageData = UIImageJPEGRepresentation(imageToSave, 0.1)
let imageFile = PFFile(name:"image.png", data:imageData)
updateObject["imageFile"] = imageFile
// Save the data back to the server in a background task
updateObject.saveInBackgroundWithBlock{(success: Bool, error: NSError!) -> Void in
UIApplication.sharedApplication().endIgnoringInteractionEvents()
if success == false {
println("Error")
}
}
}
}
}
}