Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello β I'm @olemoritz on Twitter.
Pythonista crashes trying to save merged photo
-
@jm2466 the problem comes (I guess) from 'too much memory' in appex mode.
Try this script in normal mode and see also how we have to save a new photo now (no more photos.save_image, at least in the help of the beta)
Obviously, like @ccc adviced, you could delete each image as soon it has been pasted.import appex import console from PIL import Image from PIL.ExifTags import TAGS import photos import os import ui # standard get exif code - needed for image size def get_exif(fn): ret = {} i = Image.open(fn) info = i._getexif() for tag, value in info.items(): decoded = TAGS.get(tag, tag) ret[decoded] = value return ret class MyView(ui.View): def __init__(self): self.frame = (0,0,500,500) if not appex.is_running_extension(): assets = photos.get_assets() assets = photos.pick_asset(assets=assets,multi=True) images = [] for asset in assets: images.append(asset.get_image()) else: images = appex.get_images(image_type='pil') print(f'len(images) = {len(images)})') #console.hud_alert(str(len(images))) # get the widths/heights of the images widths, heights = zip(*(i.size for i in images)) # this is stacking vertically so the width is the max width of all the pics max_width = max(widths) # since stacking vertically the height is the total heights of the pics total_height = sum(heights) print(f'total height = {total_height}') #console.hud_alert(str(total_height)) print(f'max width = {max_width}') #console.hud_alert(str(max_width)) # create a new blank image in the required size console.hud_alert(str(4*max_width*total_height)) #return new_im = Image.new('RGBA', (max_width, total_height)) print(f'new_im.size = {new_im.size}') console.hud_alert(str(new_im.size)) # now assemble the merged pic # y_offset controls the pixel location of where each image is added # to the new image - to add each end-to-end # starting at the top which is y = 0 y_offset = 0 # loop through all passed images for im in images: # paste in the image at the left edge (x=0) and current offset for y new_im.paste(im, (0,y_offset)) print(f'y_offset = {y_offset}') console.hud_alert(str(y_offset)) # increase the offset by the height of current pic y_offset += im.size[1] # save the new image to the camera roll new_im.show() #photos.save_image(new_im) path = 'temp.jpg' new_im.save(path , quality=95) photos.create_image_asset(path) os.remove(path) def main(): v = MyView() v.present('sheet') if __name__ == '__main__': main()
-
@JonB unfortunately there is no log generated π’ , like often for crash due to memory problems
-
Sorry for the delay in replying and thanks for the help.
So from the above suggestions it appears deleting the photos once merged may be the best solution but I am not quite sure how to do that correctly. This is an extension so will receive the selected photos that I believe are just references/names of the photos and not the actual image - is that correct? My code then loops through the list and actually gets the photos and adds to a list. First it sounds like I should not get all of them before processing but rather merge each one as it is retrieved. Once that is done I need to βdeleteβ it in the program, or really just delete/remove it from program memory what is the best way to do this without deleting the actual photo?
Thanks
-
for asset in assets: image = asset.get_image() images.append(image) del image
-
Thanks!!
-
I added the del image after it is pasted into the merged image and that part appears to work. The program is now having a problem saving the merged image to the camera roll. If I instead save it to the Pythonista file system it works and is a workaround until the camera roll save is figured out. Any other thoughts on how to fix the camera roll save is greatly appreciated.
-
Do you have a GitHub repo? It is more difficult to debug English prose that it is to debug Python code.
-
Unfortunately I do not.
But if you look at the code in my original post I added one line in the for loop to delete the image (below is the loop from that post with the del at the end)
for im in images: # paste in the image at the left edge (x=0) and current offset for y new_im.paste(im, (0,y_offset)) print(f'y_offset = {y_offset}') # increase the offset by the height of current pic y_offset += im.size[1] del im
As mentioned that seemed to fix the issue pasting more than a few images into the new one.
This line crashes Pythonista:
photos.save_image(new_im)
But I can save it to the Pythonista file system with the following:
new_im.save('combined.jpg')
-
This is long shot but:
del new_img photos.save(Image.open('combined.jpg'))
-
Thanks. I tried that but it crashes trying to save the image to the camera roll. I can open the jpg in Pythonista and manually add it to the camera roll. Here are the lines at the end that I added:
new_im.save('combined.jpg') del new_im photos.save_image(Image.open('combined.jpg'))
-
@jm2466 I'm in holiday, thus perhaps not able to correctly understand all (π·π·) but why did you not use
photos.create_image_asset(path)
instead of
photos.save_image(Image.open(path))
-
Um... because you never told me to until now??? :) kidding of course. But honestly have no real answer to that question. Possibly because I wanted to save an in-memory image and not have to save the file locally first (at least that was the initial plan but is not where I am at with it now). Not sure if create image asset supports that (I tried passing the variable and got an error that a str is required).
I just tried your suggestion and it works - I mean it saves the locally saved combined.jpg to the camera roll. Thanks!!!
Any thoughts on how to get this to work without saving combined.jpg first???
-
@jm2466 You could always convert your PIL Image into an ui.Image and use this kind of code to save it in camera roll
# only to have an ui.Image import ui img = ui.Image.named('test:Bridge') # Create a PHAsset from an ui.Image (not from a PIL Image) from objc_util import * import threading NSBundle.bundleWithPath_('/System/Library/Frameworks/Photos.framework').load() PHPhotoLibrary = ObjCClass('PHPhotoLibrary') PHAssetChangeRequest = ObjCClass('PHAssetChangeRequest') lib = PHPhotoLibrary.sharedPhotoLibrary() def change_block(): req = PHAssetChangeRequest.creationRequestForAssetFromImage_(img) def perform_changes(): lib.performChangesAndWait_error_(change_block, None) t = threading.Thread(target=perform_changes) t.start() t.join()
-
@jm2466 this
# 1) convert PIL Image to ui.Image console.hud_alert('convert PIL Image into ui.Image') with io.BytesIO() as bIO: new_im.save(bIO, 'PNG') ui_image = ui.Image.from_data(bIO.getvalue()) del bIO # 2) Create a PHAsset from an ui.Image (not from a PIL Image) console.hud_alert('Create a PHAsset from an ui.Image') lib = PHPhotoLibrary.sharedPhotoLibrary() def change_block(): req = PHAssetChangeRequest.creationRequestForAssetFromImage_(ui_image) def perform_changes(): lib.performChangesAndWait_error_(change_block, None) t = threading.Thread(target=perform_changes) t.start() t.join()
is about (π) 1000 x slower than
path = 'temp.jpg' new_im.save(path , quality=95) photos.create_image_asset(path) os.remove(path)