I am trying to get the depth data associated with an image in the PhotoLibrary.
I can get the image, and the URL, but I can't seem to get the aux data associated with it. The call to CGImageSourceCreateWithURL returns a source, but the call to CGImageSourceCopyAuxiliaryDataInfoAtIndex returns nil for both kCGImageAuxiliaryDataTypeDisparity and kCGImageAuxiliaryDataTypeDepth.
Is there something I am missing here?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage]
let url = info[UIImagePickerControllerImageURL]
print("url=",url)
guard let source = CGImageSourceCreateWithURL(url as! CFURL, nil) else {
return
}
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return
}
}
I struggled with this for a whole day! I finally figured it out, though, after watching the first half of the WWDC Video titled "Editing Images With Depth."
My problem was using a URL for the image that was not from the PHAsset.
Here is the link:
LINK TO WWDC VIDEO
If you don't feel like watching it, check out this function that I wrote that does pretty much exactly what is done in the video.
You have to provide the function the [info] that is returned from the DID_FINISH_PICKING_IMAGE_WITH_INFO function from the UIImagePickerDelegate.
Before using this function - note that is actually doesn't work! It is great to look at though, because it shows the steps clearly. But due to asynchronous behavior, the function will always return nil before it has a chance to set the local depth variable to the AVDepthData.
My solution was to break the function apart and use Grand Central Dispatch to create a Dispatch Group, enter it, retrieve the imageURL from the PHAsset, and then leave the Dispatch Group. Upon leaving the Dispatch Group, the DispatchGroup.NOTIFIED function then proceeded with the rest of the process.
I hope this helps!!!
func returndepthdata(usingimageinfo: [UIImagePickerController.InfoKey : Any]) -> AVDepthData? {
var depthdata: AVDepthData! = nil
if let photoasset = usingimageinfo[.phAsset] as? PHAsset {
let input = photoasset.requestContentEditingInput(with: nil, completionHandler: { (input, info) in
if let imageurl = input?.fullSizeImageURL {
if let source = CGImageSourceCreateWithURL(imageurl as CFURL, nil) {
if let imageproperties = CGImageSourceCopyProperties(source, nil) {
if let disparityinfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) {
if let truedepthdata = try? AVDepthData(fromDictionaryRepresentation: disparityinfo as! [AnyHashable : Any]) {
depthdata = truedepthdata
}
}
}
}
}
})
}
return depthdata
}
The image URL supplied by UIImagePickerController does not include any of the metadata associated with depth. To get this information, you must access the PHAsset using the PhotoBook API.
First, import the API:
import Photos
Before you display your image picker, request user access to the photo book. You'll need to add an info dictionary key for Photo Library Usage for this to work:
switch PHPhotoLibrary.authorizationStatus() {
case .notDetermined:
PHPhotoLibrary.requestAuthorization { (status) in
if status == .authorized {
DispatchQueue.main.async {
// Display image picker here
}
}
}
case .authorized: // Display image picker here
case .denied, .restricted: // Display appropriate error here
}
Now, in your image picker delegate you can do this:
if let asset = info[.phAsset] as? PHAsset {
PHImageManager.default().requestImageData(for: asset, options: nil) { (imageData, dataType, orientation, info) in
let url = info?["PHImageFileURLKey"] as? URL
// Pass this URL to your existing code.
}
}
Note that the file may contain depth or disparity information. You can convert between these easily enough, but you may need to check which one you have using CGImageSourceCopyProperties(). Also look out for the new supplementary depth data, kCGImageAuxiliaryDataTypePortraitEffectsMatte, which gives a much higher resolution mask for just the subject person in portrait images and is great for doing greenscreen-style effects.
Related
I'm trying to read workouts from a WatchOS App. Unfortunately, the returned workouts are always empty when I read the workouts on WatchOS.
This is how it's currently implemented:
func fetchWorkouts(limit: WorkoutLimit) async throws -> [HKWorkout] {
let activityType = PulseConfiguration.activityType
// 1. Get all workouts with the configured activity type.
let walkingPredictate = HKQuery.predicateForWorkouts(with: activityType)
// 2. Get all workouts that only came from this app.
let sourcePredicate = HKQuery.predicateForObjects(from: .default())
// 3. Combine the predicates into a single predicate.
let compound = NSCompoundPredicate(andPredicateWithSubpredicates:
[walkingPredictate, sourcePredicate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
typealias WorkoutsContinuation = CheckedContinuation<[HKWorkout], Error>
return try await withCheckedThrowingContinuation { (continuation: WorkoutsContinuation) in
let query = HKSampleQuery(
sampleType: .workoutType(),
predicate: compound,
limit: limit.count,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
guard let samples = samples as? [HKWorkout], error == nil else {
if let error = error {
continuation.resume(throwing: error)
}
return
}
continuation.resume(returning: samples)
}
healthStore.execute(query)
}
}
I noticed that the HKSource returned by .default (WatchOS) is different than the source associated to the saved workouts (Which contains the bundle identifier from the iPhone App).
Is there some other way to access the workouts from the WatchOS App?
Thanks in advance. Josh.
Watch has a limited subset of health data available. You can use HKHealthStore.earliestPermittedSampleDate to see how far back data you can query for.
I have an apple watch complication and the iPhone app running side by side. I have a button within the app to transmit application context dictionary to the watch. I expect to see the complication title to be refreshed.
I cannot seem to force the "tap button -> see update on the complication" kind of behavior.
What is the appropriate method to force a complication update? How can I refresh my apple watch complication instantly?
I do see the title changes, but I think it requires me to tap on the complication to open it's apple watch app first. How can I get the complication to update itself on the Watch home screen?
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
if complication.family == .graphicRectangular {
let template = CLKComplicationTemplateGraphicRectangularLargeImage()
//...configure
return template
}
}
I see this apple provided code that refreshes the complication. I'm not sure if it is too much, or if calling extendTimeline alone is sufficient if I'm generating the complication using the entry above.
func refreshComplication() {
#if os(watchOS)
let server = CLKComplicationServer.sharedInstance()
if let complications = server.activeComplications {
for complication in complications {
// Call this method sparingly. If your existing complication data is still valid,
// consider calling the extendTimeline(for:) method instead.
server.reloadTimeline(for: complication)
}
}
#endif
}
You should be able to do this by calling the refreshComplication() function from your didReceiveApplicationContext block in the file which has your WCSessionDelegate.
So if you are receiving the title via an applicationContext message your code would look something along these lines.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let updatedTitle = applicationContext["updatedTitle"] {
if let title = updateTitle as? String {
//Remeber that complicationServer.swift is a seperate process therefore you will need to store the received data somehow.
UserDefaults.standard.set(title, forKey: "complicationTitle")
refreshComplication()
}
}
}
I have a setting in my iOS App that lets the user change their target and using this method refreshed the complication with the new target almost instantly. However, I believe once your complication has used up its cpu budget nothing will happen, but hopefully that is not happening for you. See https://developer.apple.com/documentation/clockkit/clkcomplicationserver/1627891-reloadtimeline
Hope that helps, let me know how you get on.
Drew
I have 3 Labels and a UIButton in my view. After I press the UIButton some functions are being executed and I want them to do the following:
As soon as Picture 1 is finished downloading Label 1 shall be updated, after Picture 2 is finished downloading Label 2 shall be updated and so on.
The problem is: My view, so my labels, are all updated at once after all functions are resolved...
I read about asynchronous tasks - but in my understanding I don't need it because I don't mind if the user can't click any Buttons as long as all the functions are executed, all I want is the view(labels) to update while executing the functions, not only after everything is completed...
Here is my code:
#IBAction func ButtonLoadPictures(_ sender: UIButton) {
// Picture1
downloadImage()
LabelResult1.text = "Ready"
// Picture2
downloadImage()
LabelResult2.text = "Ready"
// Picture3
downloadImage()
LabelResult3.text = "Ready"
}
Edit:
Because I was asked for the code inside downloadImage():
downloadImage was just a placeholder for a lot of functions I have in this place. But in my understanding it is not relevant what the code specifically is. In case it is, I have added an example for downloadImage(). The problem is I don't want to update my labels all at once after execution of all functions, but after each time after "donwnloadImage()"
func downloadImage(){
guard let myURL = URL(string: myLink) else {
print("Error: \(myLink) doesn't seem to be a valid URL")
return
}
do {
myLinkHTMLcontent = try String(contentsOf: myURL, encoding: .ascii)
print(myLinkHTMLcontent)
} catch let error {
print("Error: \(error)")
}
The path I would choose in this case is asynchronous tasks in your logic, and use completion handlers, something like this:
IBAction func ButtonLoadPictures(_ sender: UIButton) {
// Picture1
downloadImage(){ result in
self.LabelResult1.text = result
}
// Picture2
downloadImage(){ result in
self.LabelResult2.text = result
}
// Picture3
downloadImage(){ result in
self.LabelResult3.text = result
}
}
func downloadImage(completion: #escaping(String) -> ()){
// all your logic
completion("ready")
}
I have the following wrapped in a completion block:
dbRef.child("Employees").queryOrdered(byChild: "deptid").queryEqual(toValue: "100").observe(.childAdded, with: {
snapshot in
//add each result to an array
The problem is that the completion fires for each result. I need it to fire after all results have downloaded, which means blocking. How can I go about doing this?
Try changing to :-
dbRef.child("Employees").queryOrdered(byChild: "deptid").queryEqual(toValue: "100").observeSingleEvent(of : .value, with:{ snapshot in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let deptID = each.value["deptid"] as! String
}
}
})
I can't find a satisfying answer to my question. Given an image url, I want to download it (without saving it to the disk) and to immediately upload it to an AWS Bucket. Here is my code :
self.downloadImage = function(url){
let response = HTTP.get(url, {
encoding:null // for binary
})
if (!response.headers['content-type'].split('/')[0] == 'image'){
throw new Error("not an image")
}
return {
data : response.content,
contentType : response.headers['content-type']
}
}
self.uploadImage = function(websiteName, imgUrl, callback){
// we retrieve the image
let image = downloadImage(imgUrl)
let imageName = self.extractImageName(imgUrl)
let filename = 'img/' +websiteName + "/" + imageName
let newUrl = `https://s3.${Meteor.settings.AWS.REGION}.amazonaws.com/${Meteor.settings.S3.BUCKET_NAME}/${filename}`
// makes the async function sync like
let putObjectSync = Meteor.wrapAsync(s3Client.putObject, s3Client)
try{
let res = putObjectSync({
Body: image.data,
ContentType : image.contentType,
ContentEncoding: 'base64',
Key: filename,
ACL:'public-read',
Bucket: Meteor.settings.S3.BUCKET_NAME
})
return newUrl
} catch(e) {
return null
}
}
Everything works fine, except that the image seems corrupted. So far I tried :
to use aldeed:http, in order to set the encoding to null when downloading, which seems a good strategy for images
not to use it and to pass the text content of the response directly as the upload body
to add base64 encoding in aws
Still corrupted. I feel very close to the solution, as the image as the correct type and file size, but still won't print in the browser or on my computer. Any idea about how to correctly encode/retrieve the data ?
Okay I found the answer by myself :
aldeed:meteor allow to add a responseType parameter to the get request. We simply need to set this option to buffer, so that we get the data as a buffer. Then we simply give this buffer, with no transformation, as the Body of the upload function.