class PyCameraDevice(EventDispatcher): camera_id = StringProperty() output_texture = ObjectProperty(None, allownone=True) preview_active = BooleanProperty(False) preview_texture = ObjectProperty(None, allownone=True) preview_resolution = ListProperty() preview_fbo = ObjectProperty(None, allownone=True) java_preview_surface_texture = ObjectProperty(None) java_preview_surface = ObjectProperty(None) java_capture_request = ObjectProperty(None) java_surface_list = ObjectProperty(None) java_capture_session = ObjectProperty(None) connected = BooleanProperty(False) supported_resolutions = ListProperty() # TODO: populate this facing = OptionProperty("UNKNOWN", options=["UNKNOWN", "FRONT", "BACK", "EXTERNAL"]) java_camera_characteristics = ObjectProperty() java_camera_manager = ObjectProperty() java_camera_device = ObjectProperty() java_stream_configuration_map = ObjectProperty() _open_callback = ObjectProperty(None, allownone=True) def __init__(self, **kwargs): super().__init__(**kwargs) self.register_event_type("on_opened") self.register_event_type("on_closed") self.register_event_type("on_disconnected") self.register_event_type("on_error") self._java_state_callback_runnable = Runnable( self._java_state_callback) self._java_state_java_callback = MyStateCallback( self._java_state_callback_runnable) self._java_capture_session_callback_runnable = Runnable( self._java_capture_session_callback) self._java_capture_session_java_callback = MyCaptureSessionCallback( self._java_capture_session_callback_runnable) self._populate_camera_characteristics() def on_opened(self, instance): pass def on_closed(self, instance): pass def on_disconnected(self, instance): pass def on_error(self, instance, error): pass def close(self): self.java_camera_device.close() def _populate_camera_characteristics(self): logger.info("Populating camera characteristics") self.java_stream_configuration_map = self.java_camera_characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) logger.info("Got stream configuration map") self.supported_resolutions = [ (size.getWidth(), size.getHeight()) for size in self.java_stream_configuration_map.getOutputSizes( SurfaceTexture(0).getClass()) ] logger.info("Got supported resolutions") facing = self.java_camera_characteristics.get( CameraCharacteristics.LENS_FACING) logger.info(f"Got facing: {facing}") if facing == LensFacing.LENS_FACING_BACK.value: # CameraCharacteristics.LENS_FACING_BACK: self.facing = "BACK" elif facing == LensFacing.LENS_FACING_FRONT.value: # CameraCharacteristics.LENS_FACING_FRONT: self.facing = "FRONT" elif facing == LensFacing.LENS_FACING_EXTERNAL.value: # CameraCharacteristics.LENS_FACING_EXTERNAL: self.facing = "EXTERNAL" else: raise ValueError( "Camera id {} LENS_FACING is unknown value {}".format( self.camera_id, facing)) logger.info(f"Finished initing camera {self.camera_id}") def __str__(self): return "<PyCameraDevice facing={}>".format(self.facing) def __repr__(self): return str(self) def open(self, callback=None): self._open_callback = callback self.java_camera_manager.openCamera(self.camera_id, self._java_state_java_callback, _global_handler) def _java_state_callback(self, *args, **kwargs): action = MyStateCallback.camera_action.toString() camera_device = MyStateCallback.camera_device self.java_camera_device = camera_device logger.info("CALLBACK: camera event {}".format(action)) if action == "OPENED": self.dispatch("on_opened", self) self.connected = True elif action == "DISCONNECTED": self.dispatch("on_disconnected", self) self.connected = False elif action == "CLOSED": self.dispatch("on_closed", self) self.connected = False elif action == "ERROR": error = MyStateCallback.camera_error self.dispatch("on_error", self, error) self.connected = False elif action == "UNKNOWN": print("UNKNOWN camera state callback item") self.connected = False else: raise ValueError( "Received unknown camera action {}".format(action)) if self._open_callback is not None: self._open_callback(self, action) def start_preview(self, resolution): if self.java_camera_device is None: raise ValueError( "Camera device not yet opened, cannot create preview stream") if resolution not in self.supported_resolutions: raise ValueError( "Tried to open preview with resolution {}, not in supported resolutions {}" .format(resolution, self.supported_resolutions)) if self.preview_active: raise ValueError( "Preview already active, can't start again without stopping first" ) logger.info( "Creating capture stream with resolution {}".format(resolution)) self.preview_resolution = resolution self._prepare_preview_fbo(resolution) self.preview_texture = Texture(width=resolution[0], height=resolution[1], target=GL_TEXTURE_EXTERNAL_OES, colorfmt="rgba") logger.info("Texture id is {}".format(self.preview_texture.id)) self.java_preview_surface_texture = SurfaceTexture( int(self.preview_texture.id)) self.java_preview_surface_texture.setDefaultBufferSize(*resolution) self.java_preview_surface = Surface(self.java_preview_surface_texture) self.java_capture_request = self.java_camera_device.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW) self.java_capture_request.addTarget(self.java_preview_surface) self.java_capture_request.set( CaptureRequest.CONTROL_AF_MODE, ControlAfMode.CONTROL_AF_MODE_CONTINUOUS_PICTURE.value ) # CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) self.java_capture_request.set(CaptureRequest.CONTROL_AE_MODE, ControlAeMode.CONTROL_AE_MODE_ON.value ) # CaptureRequest.CONTROL_AE_MODE_ON) self.java_surface_list = ArrayList() self.java_surface_list.add(self.java_preview_surface) self.java_camera_device.createCaptureSession( self.java_surface_list, self._java_capture_session_java_callback, _global_handler, ) return self.preview_fbo.texture def _prepare_preview_fbo(self, resolution): self.preview_fbo = Fbo(size=resolution) self.preview_fbo['resolution'] = [float(f) for f in resolution] self.preview_fbo.shader.fs = """ #extension GL_OES_EGL_image_external : require #ifdef GL_ES precision highp float; #endif /* Outputs from the vertex shader */ varying vec4 frag_color; varying vec2 tex_coord0; /* uniform texture samplers */ uniform sampler2D texture0; uniform samplerExternalOES texture1; uniform vec2 resolution; void main() { gl_FragColor = texture2D(texture1, tex_coord0); } """ with self.preview_fbo: Rectangle(size=resolution) def _java_capture_session_callback(self, *args, **kwargs): event = MyCaptureSessionCallback.camera_capture_event.toString() logger.info("CALLBACK: capture event {}".format(event)) self.java_capture_session = MyCaptureSessionCallback.camera_capture_session if event == "READY": logger.info("Doing READY actions") self.java_capture_session.setRepeatingRequest( self.java_capture_request.build(), None, None) Clock.schedule_interval(self._update_preview, 0.) def _update_preview(self, dt): self.java_preview_surface_texture.updateTexImage() self.preview_fbo.ask_update() self.preview_fbo.draw() self.output_texture = self.preview_fbo.texture
class Slide(Factory.ButtonBehavior, Factory.Image): ctrl = ObjectProperty(None) slide_rotation = NumericProperty(0) slide_scale = NumericProperty(1.) slide_pos = ListProperty([0, 0]) selected = BooleanProperty(False) index = NumericProperty(0) def __init__(self, **kwargs): # get raw rgb thumb is available self.thumb = kwargs.get('thumb', None) del kwargs['thumb'] # extract controler now, we need it. self.ctrl = kwargs.get('ctrl') # create fbo for tiny texture self.fbo = Fbo(size=(160, 120)) with self.fbo: Color(1, 1, 1) Rectangle(size=self.fbo.size) self.fborect = Rectangle(size=self.fbo.size) if self.thumb: self.upload_thumb() else: self.update_capture() super(Slide, self).__init__(**kwargs) def on_press(self, touch): if touch.is_double_tap: self.ctrl.remove_slide(self) else: self.ctrl.select_slide(self) def update_capture(self, *largs): edit_mode = self.ctrl.is_edit self.ctrl.is_edit = False # update main fbo fbo = self.ctrl.capture.fbo fbo.ask_update() fbo.draw() # update our tiny fbo self.fborect.texture = fbo.texture self.fbo.ask_update() self.fbo.draw() # then bind the texture to our texture image self.texture = self.fbo.texture self.texture_size = self.texture.size self.ctrl.is_edit = edit_mode self.ctrl.set_dirty() self.thumb = None def download_thumb(self): if self.thumb is None: fbo = self.fbo fbo.draw() fbo.bind() tmp = glReadPixels(0, 0, fbo.size[0], fbo.size[1], GL_RGBA, GL_UNSIGNED_BYTE) fbo.release() # remove alpha tmp = list(tmp) del tmp[3::4] tmp = ''.join(tmp) self.thumb = (fbo.size[0], fbo.size[1], tmp) def upload_thumb(self): from kivy.graphics.texture import Texture w, h, pixels = self.thumb texture = Texture.create((w, h), 'rgb', 'ubyte') texture.blit_buffer(pixels, colorfmt='rgb') self.texture = texture self.texture_size = texture.size
class Slide(Factory.ButtonBehavior, Factory.Image): ctrl = ObjectProperty(None) slide_rotation = NumericProperty(0) slide_scale = NumericProperty(1.) slide_pos = ListProperty([0,0]) selected = BooleanProperty(False) index = NumericProperty(0) def __init__(self, **kwargs): # get raw rgb thumb is available self.thumb = kwargs.get('thumb', None) del kwargs['thumb'] # extract controler now, we need it. self.ctrl = kwargs.get('ctrl') # create fbo for tiny texture self.fbo = Fbo(size=(160, 120)) with self.fbo: Color(1, 1, 1) Rectangle(size=self.fbo.size) self.fborect = Rectangle(size=self.fbo.size) if self.thumb: self.upload_thumb() else: self.update_capture() super(Slide, self).__init__(**kwargs) def on_press(self, touch): if touch.is_double_tap: self.ctrl.remove_slide(self) else: self.ctrl.select_slide(self) def update_capture(self, *largs): edit_mode = self.ctrl.is_edit self.ctrl.is_edit = False # update main fbo fbo = self.ctrl.capture.fbo fbo.ask_update() fbo.draw() # update our tiny fbo self.fborect.texture = fbo.texture self.fbo.ask_update() self.fbo.draw() # then bind the texture to our texture image self.texture = self.fbo.texture self.texture_size = self.texture.size self.ctrl.is_edit = edit_mode self.ctrl.set_dirty() self.thumb = None def download_thumb(self): if self.thumb is None: fbo = self.fbo fbo.draw() fbo.bind() tmp = glReadPixels(0, 0, fbo.size[0], fbo.size[1], GL_RGBA, GL_UNSIGNED_BYTE) fbo.release() # remove alpha tmp = list(tmp) del tmp[3::4] tmp = ''.join(tmp) self.thumb = (fbo.size[0], fbo.size[1], tmp) def upload_thumb(self): from kivy.graphics.texture import Texture w, h, pixels = self.thumb texture = Texture.create((w, h), 'rgb', 'ubyte') texture.blit_buffer(pixels, colorfmt='rgb') self.texture = texture self.texture_size = texture.size