Rotate image in share extension - ios

I have this extension and it works perfect in app target but crash in share extension when trying to rotate image captured on camera. How to rotate image in share extension? Or maybe it possible to load image from Photo Library already in right orientation.
extension UIImage {
func fixOrientation() -> UIImage {
switch imageOrientation {
case .up:
return self
default:
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size)) //Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
}
}
Crash screenshots:

First of all it is clear that you have a memory crash. According to App Extension Programming Guide:
Memory limits for running app extensions are significantly lower than the memory limits imposed on a foreground app. On both platforms, the system may aggressively terminate extensions because users want to return to their main goal in the host app.
And from error it is clear that you exceed 120 mb. But you might wonder what is took so much memory.
According to Optimizing Images
Written by Jordan Morgan:
iOS essentially derives its memory hit from an image’s dimensions - whereas the actual file size has much less to do with it.
So if we calculate size or 4032 x 3024 photo it will be... 46mb for 4 bit color and 79mb for 8 bit color. Pretty big, but still less that a limit...
Thing is - you have two copies of your image. One is original and second one - rotated.
To solve this issue you need load only rotated image into memory, without original. This can be done with Image I/O Framework:
extension UIImage {
static func imageWithFixedOrientation(at url: URL) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
guard let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? Dictionary<CFString, Any> else { return nil }
guard
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat
else { return nil }
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(width, height),
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true
]
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: cgImage)
}
}
In sample app:
extension ViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let url = info[.imageURL] as? URL else { return }
let image = UIImage.imageWithFixedOrientation(at: url)
}
}
it reduced memory peaks from 180+mb to just 80mb.

You may try to use more optimized UIGraphicsImageRendererFormat instead of using old UIGraphicsBeginImageContextWithOptions, you can find more information in this post. So your code will look something like below:
func fixOrientation() -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
format.prefersExtendedRange = true
let renderer = UIGraphicsImageRenderer(size: size, format: format)
let image = renderer.image(actions: { context in
var workSize = size;
workSize.width = floor(workSize.width / scale)
workSize.height = floor(workSize.height / scale)
// if the orientation is already correct
// if image.imageOrientation == .up { draw image }
var transform = CGAffineTransform.identity
//TO-DO - set transform depends on current image orientation
//transform =
let ctx = context.cgContext
ctx.concatenate(transform)
guard let cgImageCopy = cgImage else {
return
}
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImageCopy, in: CGRect(x: 0.0, y:0.0, width: workSize.height, height: workSize.width))
break;
default:
ctx.draw(cgImageCopy, in: CGRect(origin: .zero, size: workSize))
break;
}
})
return image
}

Did you try using the function you provided within an didFinishPicking delegate method?
func photoLibrary() {
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = .photoLibrary
self.present(myPickerController, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[.originalImage] as? UIImage else {
print("No image found")
return
}
// Flip the image here
self.mainImageView.image = image.fixOrientation()
}

Related

CGImageDestinationCreateWithData doubles pixel dimension

If I create a UIImage 'image' from a JPEG file selection in iOS using
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
if let image = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.originalImage)] as? UIImage {
}
}
And then create a data object from image using
imageData = image.jpegData(compressionQuality:1.0)
and then add some properties using
func addImageProperties(imageData: Data, properties: CFDictionary) -> Data {
if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
if let uti = CGImageSourceGetType(source) {
let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) {
CGImageDestinationAddImageFromSource(destination, source, 0, properties)
if CGImageDestinationFinalize(destination) == false {
return imageData // return input data if error
}
let destinationDataResult = destinationData as Data
return destinationDataResult
}
}
}
return imageData // return input data if error
}
and then save the data to a file using
func saveImageDataAsImage(_ data: Data) {
var newImageIdentifier: String!
PHPhotoLibrary.shared().performChanges{
let assetRequest = PHAssetCreationRequest.forAsset()
assetRequest.addResource(with: .photo, data: data, options: nil)
newImageIdentifier = assetRequest.placeholderForCreatedAsset!.localIdentifier
} completionHandler: { (success, error) in
DispatchQueue.main.async(execute: {
if success, let newAsset = PHAsset.fetchAssets(withLocalIdentifiers: [newImageIdentifier], options: nil).firstObject {
// ...
} else {
// ...
}
})
}
}
The resulting file has twice the width and height in pixels of the original selected file. How can I retain the original file width and height while adding the required metadata?
The loaded image (with UIImage.scale = 1.0) was being processed before saved in the following code, which reset the UIImage.scale value to 2.0. Code to reset UIImage.scale after this it did not change the image.cgImage size, but changing the line let scale = UIScreen.main.scale to let scale:CGFloat = 1.0 in the code resulted in the saved image having the same pixel dimensions as the input image.
func textToImage(drawText: String, inImage: UIImage, atPoint: CGPoint, textColor: UIColor, textFont: UIFont, backColor: UIColor) -> UIImage{
// Setup the font specific variables
//var textColor = UIColor.white
//var textFont = UIFont(name: "Helvetica Bold", size: 12)!
// Setup the image context using the passed image
let scale = UIScreen.main.scale// change to let scale:CGFloat = 1.0 to fix problem
UIGraphicsBeginImageContextWithOptions(inImage.size, false, scale)
// Setup the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.backgroundColor: backColor,
]
// Put the image into a rectangle as large as the original image
inImage.draw(in: CGRect(x: 0, y: 0, width: inImage.size.width, height: inImage.size.height))
// Create a point within the space that is as bit as the image
var rect = CGRect(x: atPoint.x, y: atPoint.y, width: inImage.size.width, height: inImage.size.height)
// Draw the text into an image
drawText.draw(in: rect, withAttributes: textFontAttributes)
// Create a new image out of the images we have created
var newImage = UIGraphicsGetImageFromCurrentImageContext()
// End the context now that we have the image we need
UIGraphicsEndImageContext()
//Pass the image back up to the caller
return newImage!
}

UIImage is rotated 90 degrees when creating from url and set to the pasteboard

What do I simply do?
let pasteboard = UIPasteboard.general
let base64EncodedImageString = "here_base_64_string_image"
let data = Data(base64Encoded: base64EncodedImageString)
let url = data?.write(withName: "image.jpeg")
pasteboard.image = UIImage(url: url) //and now when I try to paste somewhere that image for example in imessage, it is rotated... why?
What may be important:
It happens only for images created by camera.
However, if use exactly the same process (!) to create activityItems for UIActivityViewController and try to use iMessage app, then it works... why? What makes the difference?
I use above two simple extensions for UIImage and Data:
extension Data {
func write(withName name: String) -> URL {
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(name)
do {
try write(to: url, options: NSData.WritingOptions.atomic)
return url
} catch {
return url
}
}
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else {
return nil
}
do {
self.init(data: try Data(contentsOf: url))
} catch {
return nil
}
}
}
Before server returns base64EncodedString I upload an image from camera like this:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
let image = info[.originalImage] as? UIImage
let encodedBase64 = image?.jpegData(compressionQuality: 0.9)?.base64EncodedString() ?? ""
//upload encodedBase64 to the server... that is all
}
I am not sure but I think UIPasteBoard converts your image to PNG and discards its orientation. You can explicitly tell the kind of data you are adding to the pasteboard but I am not sure if this would work for your scenery.
extension Data {
var image: UIImage? { UIImage(data: self) }
}
setting your pasteboard data
UIPasteboard.general.setData(jpegData, forPasteboardType: "public.jpeg")
loading the data from pasteboard
if let pbImage = UIPasteboard.general.data(forPasteboardType: "public.jpeg")?.image {
}
Or Redrawing your image before setting your pasteboard image property
extension UIImage {
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
UIPasteboard.general.image = image.flattened()

How do I create a croppable banner image in uiimagepickercontroller?

When I present a UIImagePickerController, I want the user to be able to crop a 7.8 ratio for the banner that the user wants to import from their photo library.
This question is similar to many questions relating to creating custom crop rects built to work with UIImagePickerController but all the answers point to outdated libraries or libraries with too much complexity. I want something simple.
Try this:
open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var newImage: UIImage
if picker.sourceType == .camera {
if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
// save photo
}
}
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad && picker.sourceType == .photoLibrary {
if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
}
} else {
if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
newImage = possibleImage
} else if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
}
}
// logic to manipulate image
print("image size: \(newImage.size)")
let newHeight = 100 // change to preferred height
let scale = 7.8
let newWidth = newImage.size.width
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
newImage.draw(in: CGRect(x: 0, y: 0,width: newWidth, height: newHeight))
newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
print("image size: \(newImage.size)")
// add logic here
}

how to reduce photo size and send to myserver in parameters in swift4

i'm using the UIImagePickerController to take photo and im converting image to base64 and i want to send image to my server but the photo was converting to base64 but the problem is i can't send the converted photo to my server in paramenter
here my code
#IBOutlet weak var CameraOutlet: UIButton!
#IBOutlet weak var Complaint_Image1: UIImageView!
#IBOutlet weak var Complaint_Image2: UIImageView!
#IBOutlet weak var Complaint_Image3: UIImageView!
func TakePhotos() {
let Image = UIImagePickerController()
Image.allowsEditing = true
Image.sourceType = UIImagePickerControllerSourceType.camera
Image.delegate = self
present(Image, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let picture = info[UIImagePickerControllerOriginalImage] as? UIImage {
if (Complaint_Image1.image != nil && Complaint_Image2.image != nil) {
Complaint_Image3.image = picture
}
else if Complaint_Image1.image != nil {
Complaint_Image2.image = picture
}
else {
Complaint_Image1.image = picture
}
}
if Complaint_Image1.image != nil {
let imageData:NSData = UIImagePNGRepresentation(Complaint_Image1.image!)! as NSData
let imageStr = imageData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let imageBase64 = imageStr
let imgstr2 = String.init(format: "data:image/png;base64,%#", imageBase64)
UserDefaults.standard.set(imgstr2, forKey: "image")
UserDefaults.standard.synchronize()
}
let picture1 = UserDefaults.standard.object(forKey: "image")
let parameter = ["Image1": picture1!,"Image":[["name": picture2],["name": picture3]] as [String: Any]
}
Just use following code to compress image before upload to server side
First of all, add below extension of uiimage to any class or singleton class
extension UIImage {
//MARK:- convenience function in UIImage extension to resize a given image
func convert(toSize size:CGSize, scale:CGFloat) ->UIImage {
let imgRect = CGRect(origin: CGPoint(x:0.0, y:0.0), size: size)
UIGraphicsBeginImageContextWithOptions(size, false, scale)
self.draw(in: imgRect)
let copied = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return copied!
}
}
and then write below methods in controller where pick images
//MARK:- ImagePicker Delegate Methods
internal func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
// resize our selected image
let resizedImage = chosenImage.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale)
profileImg.image = resizedImage
dismiss(animated:true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
You can use UIImageJPEGRepresentation compressionQualityto reduce photo size.
let picture = info[UIImagePickerControllerOriginalImage] as! UIImage
// USE ANY OF 1 FROM BELOW TO REDUCE SIZE
let imgData1 = UIImageJPEGRepresentation(picture, 1.0)
print("1.0 size is: \(String(describing: Double((imgData1?.count)!) / 1024.0)) KB")
let imgData2 = UIImageJPEGRepresentation(picture, 0.7)
print("0.7 size is: \(String(describing: Double((imgData2?.count)!) / 1024.0)) KB")
let imgData3 = UIImageJPEGRepresentation(picture, 0.4)
print("0.4 size is: \(String(describing: Double((imgData3?.count)!) / 1024.0)) KB")
let imgData4 = UIImageJPEGRepresentation(picture, 0.0)
print("0.0 size is: \(String(describing: Double((imgData4?.count)!) / 1024.0)) KB")
// BASE64 STRING
let img1stBase64: String = (imgData2?.base64EncodedString(options: .lineLength64Characters))!
let img2ndBase64: String = (imgData3?.base64EncodedString(options: .lineLength64Characters))!
let img3rdBase64: String = (imgData4?.base64EncodedString(options: .lineLength64Characters))!
let parameter = ["Image1": img1stBase64,"Image":[["name": img2ndBase64],["name": img3rdBase64]]] as [String: Any]
OUTPUT
Here you can clearly know the difference between originalImage size and reduced image size from below.
1.0 size is: 3177.0869140625 KB
0.7 size is: 892.78125 KB
0.4 size is: 363.2890625 KB
0.0 size is: 180.8818359375 KB

How I can clear memory when use Camera?

I'm trying to create a camera app but got memory leak.
I can clear cache of PhotoLibrary but cant clear cache of Camera.
App will down after take about 40 photos.
How can I clear memory?
// After taking a picture
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if info[UIImagePickerControllerOriginalImage] != nil {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
var orientation: Int?
if isNewPhoto == true {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(BaseResultViewController.image(_:didFinishSavingWithError:contextInfo:)), nil)
let metadata = info[UIImagePickerControllerMediaMetadata] as? NSDictionary
orientation = metadata?.objectForKey("Orientation") as? Int
}
else {
switch image.imageOrientation.rawValue {
case 0:
orientation = 1
case 1:
orientation = 3
case 2:
orientation = 8
case 3:
orientation = 6
default:
break
}
}
let uuid = NSUUID().UUIDString
let imageName = "\(uuid).PNG"
let jpgImageName = "\(uuid)-thumb.JPG"
let data = UIImagePNGRepresentation(image)
let jpgData = UIImageJPEGRepresentation(image, 0.7)
let localPath = relationData.setFile(imageName, data: data!)
let _ = relationData.setFile(jpgImageName, data: jpgData!)
getViewIns()?.hideActionSheet()
baseModel.saveValuable(NSURL(fileURLWithPath: localPath), orientation: orientation)
prepareSegue(true)
resetValuable()
}
picker.dismissViewControllerAnimated(true, completion: nil)
}

Resources