Forum Archive

Take photo without 'Use' step, join ObjC with Photos module

AlanE

I seek help on merging an ObjC bridge program, which Ole offered some time ago, with the standard photos.capture_image

These three lines of my coding work how I want but the iOS insists on me pressing the camera button, then touching 'Use Photo'. I can then work with the output it gives.

import photos
theImage=photos.capture_image()
theImage.show()

So I found Ole's excellent Objective-C bridge program which takes a photo when told to by the program, quoted below –

from objc_util import *
import time
import threading
C = ObjCClass

def take_photo_now(filename='photo.jpg'):
    session = C('AVCaptureSession').new().autorelease()
    session.sessionPreset = 'AVCaptureSessionPresetPhoto'
    device = C('AVCaptureDevice').defaultDeviceWithMediaType_('vide')
    device_input = C('AVCaptureDeviceInput').deviceInputWithDevice_error_(device, None)
    session.addInput_(device_input)
    image_output = C('AVCaptureStillImageOutput').new().autorelease()
    session.addOutput_(image_output)
    session.startRunning()
    # NOTE: You may need to adjust this to wait for the camera to be ready (use a higher number if you see black photos):
    time.sleep(0.1)
    def handler_func(_block, _buffer, _err):
        buffer = ObjCInstance(_buffer)
        img_data = C('AVCaptureStillImageOutput').jpegStillImageNSDataRepresentation_(buffer)
        img_data.writeToFile_atomically_(filename, True)
        e.set()
    video_connection = None
    for connection in image_output.connections():
        for port in connection.inputPorts():
            if str(port.mediaType()) == 'vide':
                video_connection = connection
                break
        if video_connection:
            break
    e = threading.Event()
    handler = ObjCBlock(handler_func, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])
    retain_global(handler)
    image_output.captureStillImageAsynchronouslyFromConnection_completionHandler_(video_connection, handler)
    e.wait()


take_photo_now('photo.jpg')
import console
console.quicklook('photo.jpg')

But, hopelessly I cannot work out how to change the ObjC code to return a reference to the photo which my code can handle. That is to say that I don't know how to get 'photo.jpg' into my theImage (variable?) so I can show() it, and use it in the Image and ImageDraw modules. I wish for it happen quickly so I guess it needs to stay in a buffer and not be delayed by saving to the camera roll.

Many thanks.

cvp

@AlanE try

        img_data = C('AVCaptureStillImageOutput').jpegStillImageNSDataRepresentation_(buffer)
        ui_image = ui.Image.from_data(nsdata_to_bytes(img_data))
        ui_image.show()
        #img_data.writeToFile_atomically_(filename, True)

This gives an ui.Image, if you want a PIL image, use

def ui2pil(ui_img):
  return PILImage.open(io.BytesIO(ui_img.to_png()))
cvp

@AlanE Is that what you hoped?

ccc

To avoid running out of memory when dealing with many images on Pythonista, I believe that following is safer because https://docs.python.org/3/library/io.html#io.BytesIO only frees its memory when .close() is called.

def ui2pil(ui_img):
    with io.BytesIO(ui_img.to_png()) as mem_file:
        return PILImage.open(mem_file.getvalue())
AlanE

Thank you but I am afraid I need my hand held some more. The block of four lines which I presume replace the block of four lines in the handler function does indeed show the image. But the same apparently identical show() repeated outside of the ObjC def fails. I have put comments at the end of the troublesome line.

from objc_util import *
import time
import threading
import ui
import photos

C = ObjCClass

def take_photo_now(filename='photo.jpg'):
    session = C('AVCaptureSession').new().autorelease()
    session.sessionPreset = 'AVCaptureSessionPresetPhoto'
    device = C('AVCaptureDevice').defaultDeviceWithMediaType_('vide')
    device_input = C('AVCaptureDeviceInput').deviceInputWithDevice_error_(device, None)
    session.addInput_(device_input)
    image_output = C('AVCaptureStillImageOutput').new().autorelease()
    session.addOutput_(image_output)
    session.startRunning()
    # NOTE: You may need to adjust this to wait for the camera to be ready (use a higher number if you see black photos):
    time.sleep(0.1)
    def handler_func(_block, _buffer, _err):
        buffer = ObjCInstance(_buffer)

        img_data = C('AVCaptureStillImageOutput').jpegStillImageNSDataRepresentation_(buffer)
        ui_image = ui.Image.from_data(nsdata_to_bytes(img_data))
        ui_image.show()  # this show() works
        #img_data.writeToFile_atomically_(filename, True)

        e.set()
    video_connection = None
    for connection in image_output.connections():
        for port in connection.inputPorts():
            if str(port.mediaType()) == 'vide':
                video_connection = connection
                break
        if video_connection:
            break
    e = threading.Event()
    handler = ObjCBlock(handler_func, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])
    retain_global(handler)
    image_output.captureStillImageAsynchronouslyFromConnection_completionHandler_(video_connection, handler)
    e.wait()


# my code calls the take_photo_now def above then wishes to go on to ImageDraw on top of it –

take_photo_now('photo.jpg')

ui_image.show()    #this apparently identical show() outside of the subroutine does not work. "name' ui_images' is not defined", or if I declare at the top with "ui_image=None" then it errors with "NoneType' object has no attribute 'show'". the show() in the ObjC continues to work on both variations.
cvp

@AlanE your ui_image is local in your handler...

AlanE

@cvp Oh crikey. I fear I am lost. Does that mean that ui_image cannot come out of the ObjC for use elsewhere?

cvp

@AlanE try

from objc_util import *
import time
import threading
import ui
import photos

C = ObjCClass

def take_photo_now():
    global ui_image
    session = C('AVCaptureSession').new().autorelease()
    session.sessionPreset = 'AVCaptureSessionPresetPhoto'
    device = C('AVCaptureDevice').defaultDeviceWithMediaType_('vide')
    device_input = C('AVCaptureDeviceInput').deviceInputWithDevice_error_(device, None)
    session.addInput_(device_input)
    image_output = C('AVCaptureStillImageOutput').new().autorelease()
    session.addOutput_(image_output)
    session.startRunning()
    # NOTE: You may need to adjust this to wait for the camera to be ready (use a higher number if you see black photos):
    time.sleep(0.1)
    ui_image = None
    def handler_func(_block, _buffer, _err):
        global ui_image
        buffer = ObjCInstance(_buffer)

        img_data = C('AVCaptureStillImageOutput').jpegStillImageNSDataRepresentation_(buffer)
        ui_image = ui.Image.from_data(nsdata_to_bytes(img_data))

        e.set()
    video_connection = None
    for connection in image_output.connections():
        for port in connection.inputPorts():
            if str(port.mediaType()) == 'vide':
                video_connection = connection
                break
        if video_connection:
            break
    e = threading.Event()
    handler = ObjCBlock(handler_func, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])
    retain_global(handler)
    image_output.captureStillImageAsynchronouslyFromConnection_completionHandler_(video_connection, handler)
    e.wait()
    return ui_image


# my code calls the take_photo_now def above then wishes to go on to ImageDraw on top of it –

ui_image = take_photo_now()

ui_image.show()    #this apparently identical show() outside of the subroutine does not work. "name' ui_images' is not defined", or if I declare at the top with "ui_image=None" then it errors with "NoneType' object has no attribute 'show'". the show() in the ObjC continues to work on both variations.
cvp

@ccc said

get_value()

Do you mean getvalue()?

AlanE

@cvp Oh yes! that edit with the return line works. Completely over the top of my head but thankfully there are people who can. Thank you again.

cvp

@AlanE the global passes the ui_image from the handler to your take_photo_now function.
The return passes the same ui_image to the main, easy, isn'it?

AlanE

@cvp Easier In hindsight yes. But there will always be people faster than me, stronger than me, and in your case clearer-thinking than me, if not also faster and stronger. I now recall disliking the need for global in another program wot I wrote. In this case I think I was also frightened at the complexity of the ObjC.

cvp

@ccc said

def ui2pil(ui_img):
with io.BytesIO(ui_img.to_png()) as mem_file:
return PILImage.open(mem_file.getvalue())

Not sure that your code works, doesn't PILImage.open need a path?

I know the reverse pil2ui

def pil2ui(imgIn):
    with io.BytesIO() as bIO:
        imgIn.save(bIO, 'PNG')
        imgOut = ui.Image.from_data(bIO.getvalue())
    return imgOut