From 4a6c14cabee3bfb52a7ed397a951c0515cd5dcba Mon Sep 17 00:00:00 2001 From: maniek2332 Date: Sat, 3 Apr 2021 18:13:07 +0200 Subject: [PATCH 1/3] updated engine methods to match capturing functionality, added basic capturing wrapper in cython --- src/kaa/CMakeLists.txt | 2 + src/kaa/_kaa.pyx | 1 + src/kaa/capture.pxi | 80 +++++++++++++++++++++++++++++++++++++ src/kaa/engine.pxi | 20 +++++++++- src/kaa/kaacore/capture.pxd | 15 +++++++ src/kaa/kaacore/engine.pxd | 7 +++- 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/kaa/capture.pxi create mode 100644 src/kaa/kaacore/capture.pxd diff --git a/src/kaa/CMakeLists.txt b/src/kaa/CMakeLists.txt index 67be3dfb..bc1bf420 100644 --- a/src/kaa/CMakeLists.txt +++ b/src/kaa/CMakeLists.txt @@ -31,6 +31,7 @@ set(CYTHON_FILES shaders.pxi materials.pxi statistics.pxi + capture.pxi kaacore/__init__.pxd kaacore/engine.pxd @@ -62,6 +63,7 @@ set(CYTHON_FILES kaacore/resources.pxd kaacore/textures.pxd kaacore/statistics.pxd + kaacore/capture.pxd extra/include/pythonic_callback.h extra/include/python_exceptions_wrapper.h diff --git a/src/kaa/_kaa.pyx b/src/kaa/_kaa.pyx index 59d80eb8..5fa7e257 100644 --- a/src/kaa/_kaa.pyx +++ b/src/kaa/_kaa.pyx @@ -27,6 +27,7 @@ include "window.pxi" include "audio.pxi" include "timers.pxi" include "statistics.pxi" +include "capture.pxi" include "engine.pxi" include "shaders.pxi" include "materials.pxi" diff --git a/src/kaa/capture.pxi b/src/kaa/capture.pxi new file mode 100644 index 00000000..a1d4a4de --- /dev/null +++ b/src/kaa/capture.pxi @@ -0,0 +1,80 @@ +from libcpp.memory cimport unique_ptr +from cython.view cimport array as cvarray + +from .kaacore.capture cimport CCapturingAdapterBase, CMemoryVectorCapturingAdapter + +import base64 +from io import BytesIO + + +cdef class CapturingWrapperBase: + cdef unique_ptr[CCapturingAdapterBase] capturing_adapter + + cdef CCapturingAdapterBase* c_get_adapter(self): + assert self.capturing_adapter.get() != NULL + return self.capturing_adapter.get() + + def get_result(self): + raise NotImplementedError + + +cdef class AnimatedGifCapturingWrapper(CapturingWrapperBase): + def __init__(self): + self.capturing_adapter = \ + unique_ptr[CCapturingAdapterBase](new CMemoryVectorCapturingAdapter()) + + @property + def frames_memoryviews(self): + cdef CMemoryVectorCapturingAdapter* c_capturing_adapter = \ + self.capturing_adapter.get() + cdef cvarray image_array + cdef uint8_t* frame_data + cdef tuple frame_shape = self.frame_shape + cdef list collected_memoryviews = [] + + for frame_data in c_capturing_adapter.frames_uint8(): + image_array = cvarray( + shape=frame_shape, + itemsize=4, format='I', mode='c', allocate_buffer=False, + ) + image_array.data = frame_data + collected_memoryviews.append(image_array.get_memview()) + return collected_memoryviews + + @property + def frame_shape(self): + cdef CMemoryVectorCapturingAdapter* c_capturing_adapter = \ + self.capturing_adapter.get() + return (c_capturing_adapter.width(), c_capturing_adapter.height()) + + def get_result(self): + from PIL import Image + + cdef object bytes_buffer = BytesIO() + cdef list images = [Image.frombuffer('RGBA', memview.shape, memview) + for memview in self.frames_memoryviews] + + images[0].save(bytes_buffer, + format='gif', + save_all=True, + append_images=images[1:], + duration=50, + loop=0) + return AnimatedGifCaptureResult(bytes_buffer) + + +cdef class AnimatedGifCaptureResult: + cdef object bytes_buffer + + def __init__(self, object bytes_buffer not None): + self.bytes_buffer = bytes_buffer + + def _repr_html_(self): + self.bytes_buffer.seek(0) + return ''.format( + base64.b64encode(self.bytes_buffer.read()).decode('ascii') + ) + + +cdef CapturingWrapperBase c_get_default_capturing_wrapper(): + return AnimatedGifCapturingWrapper() diff --git a/src/kaa/engine.pxi b/src/kaa/engine.pxi index bcd2d62a..6fab0585 100644 --- a/src/kaa/engine.pxi +++ b/src/kaa/engine.pxi @@ -12,6 +12,7 @@ from .kaacore.engine cimport ( CVirtualResolutionMode ) from .kaacore.display cimport CDisplay +from .kaacore.capture cimport CCapturingAdapterBase from .kaacore.log cimport c_emit_log_dynamic, CLogLevel, _log_category_wrapper from . import __version__ @@ -97,7 +98,24 @@ cdef class _Engine: CScene* c_scene = scene._c_scene.get() CEngine* c_engine = get_c_engine() with nogil: - c_engine.run(c_scene) + c_engine.run(c_scene, 0, NULL) + + def run_capture( + self, Scene scene not None, uint32_t frames_limit, + CapturingWrapperBase capturing_wrapper = None, + ): + cdef: + CScene* c_scene = scene._c_scene.get() + CEngine* c_engine = get_c_engine() + CapturingWrapperBase final_capturing_wrapper = ( + capturing_wrapper if capturing_wrapper is not None + else c_get_default_capturing_wrapper() + ) + CCapturingAdapterBase* c_capture_adapter = final_capturing_wrapper.c_get_adapter() + with nogil: + c_engine.run(c_scene, frames_limit, c_capture_adapter) + + return final_capturing_wrapper.get_result() def quit(self): get_c_engine().quit() diff --git a/src/kaa/kaacore/capture.pxd b/src/kaa/kaacore/capture.pxd new file mode 100644 index 00000000..5be12ce4 --- /dev/null +++ b/src/kaa/kaacore/capture.pxd @@ -0,0 +1,15 @@ +from libcpp.memory cimport unique_ptr +from libcpp.vector cimport vector +from libc.stdint cimport uint8_t, uint32_t + +from .exceptions cimport raise_py_error + + +cdef extern from "kaacore/capture.h" namespace "kaacore" nogil: + cdef cppclass CCapturingAdapterBase "kaacore::CapturingAdapterBase": + uint32_t width() except +raise_py_error + uint32_t height() except +raise_py_error + + cdef cppclass CMemoryVectorCapturingAdapter "kaacore::MemoryVectorCapturingAdapter"(CCapturingAdapterBase): + size_t frames_count() except +raise_py_error + vector[uint8_t*] frames_uint8() except +raise_py_error diff --git a/src/kaa/kaacore/engine.pxd b/src/kaa/kaacore/engine.pxd index d43f4a4c..09c3ab0c 100644 --- a/src/kaa/kaacore/engine.pxd +++ b/src/kaa/kaacore/engine.pxd @@ -4,6 +4,7 @@ from libcpp.vector cimport vector from libc.stdint cimport int32_t, uint64_t from .clock cimport CDuration +from .capture cimport CCapturingAdapterBase from .display cimport CDisplay from .scenes cimport CScene from .window cimport CWindow @@ -45,8 +46,10 @@ cdef extern from "kaacore/engine.h" namespace "kaacore" nogil: vector[CDisplay] get_displays() except +raise_py_error CDuration total_time() except +raise_py_error double get_fps() except +raise_py_error - - void run(CScene* c_scene) except +raise_py_error + CDuration total_time() except +raise_py_error + vector[CDisplay] get_displays() except +raise_py_error + void run(CScene* c_scene, uint32_t frames_limit, + CCapturingAdapterBase* capturing_adapter) except +raise_py_error void change_scene(CScene* c_scene) except +raise_py_error void quit() except +raise_py_error From bd06962275ea7f42efebd2b5ed522ea787032eb2 Mon Sep 17 00:00:00 2001 From: maniek2332 Date: Sun, 22 Aug 2021 16:18:16 +0200 Subject: [PATCH 2/3] exposed `frame_fixed_duration` in `Engine.run_capture` --- src/kaa/engine.pxi | 11 +++++++++-- src/kaa/kaacore/engine.pxd | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/kaa/engine.pxi b/src/kaa/engine.pxi index 6fab0585..c195ac0f 100644 --- a/src/kaa/engine.pxi +++ b/src/kaa/engine.pxi @@ -1,4 +1,5 @@ import atexit +import math from enum import IntEnum from contextlib import contextmanager @@ -14,10 +15,14 @@ from .kaacore.engine cimport ( from .kaacore.display cimport CDisplay from .kaacore.capture cimport CCapturingAdapterBase from .kaacore.log cimport c_emit_log_dynamic, CLogLevel, _log_category_wrapper +from .kaacore.clock cimport CDuration from . import __version__ +cdef double DEFAULT_FIXED_FRAMETIME = 1. / 60. + + def _clean_up(): # force _c_engine_instance deletion, # so we are sure that kaacore dies before the python process @@ -98,10 +103,11 @@ cdef class _Engine: CScene* c_scene = scene._c_scene.get() CEngine* c_engine = get_c_engine() with nogil: - c_engine.run(c_scene, 0, NULL) + c_engine.run(c_scene, 0, CDuration(0.), NULL) def run_capture( self, Scene scene not None, uint32_t frames_limit, + *, double fixed_frametime = DEFAULT_FIXED_FRAMETIME, CapturingWrapperBase capturing_wrapper = None, ): cdef: @@ -112,8 +118,9 @@ cdef class _Engine: else c_get_default_capturing_wrapper() ) CCapturingAdapterBase* c_capture_adapter = final_capturing_wrapper.c_get_adapter() + CDuration c_duration = CDuration(fixed_frametime) with nogil: - c_engine.run(c_scene, frames_limit, c_capture_adapter) + c_engine.run(c_scene, frames_limit, c_duration, c_capture_adapter) return final_capturing_wrapper.get_result() diff --git a/src/kaa/kaacore/engine.pxd b/src/kaa/kaacore/engine.pxd index 09c3ab0c..11b5ccf4 100644 --- a/src/kaa/kaacore/engine.pxd +++ b/src/kaa/kaacore/engine.pxd @@ -48,7 +48,7 @@ cdef extern from "kaacore/engine.h" namespace "kaacore" nogil: double get_fps() except +raise_py_error CDuration total_time() except +raise_py_error vector[CDisplay] get_displays() except +raise_py_error - void run(CScene* c_scene, uint32_t frames_limit, + void run(CScene* c_scene, uint32_t frames_limit, CDuration frame_fixed_duration, CCapturingAdapterBase* capturing_adapter) except +raise_py_error void change_scene(CScene* c_scene) except +raise_py_error void quit() except +raise_py_error From 42e697fd74b427bcd0d2a47f8dce4e9789eca7db Mon Sep 17 00:00:00 2001 From: maniek2332 Date: Sun, 17 Oct 2021 23:58:07 +0200 Subject: [PATCH 3/3] updated capture feature bindings --- src/kaa/capture.pxi | 84 ++++++++++++++++--------------------- src/kaa/engine.pxi | 27 ++++++------ src/kaa/kaacore/capture.pxd | 12 +++--- src/kaa/kaacore/engine.pxd | 12 +++--- 4 files changed, 60 insertions(+), 75 deletions(-) diff --git a/src/kaa/capture.pxi b/src/kaa/capture.pxi index a1d4a4de..5db67bed 100644 --- a/src/kaa/capture.pxi +++ b/src/kaa/capture.pxi @@ -1,40 +1,28 @@ -from libcpp.memory cimport unique_ptr +from libcpp.vector cimport vector from cython.view cimport array as cvarray -from .kaacore.capture cimport CCapturingAdapterBase, CMemoryVectorCapturingAdapter +from .kaacore.capture cimport CCapturedFrames import base64 from io import BytesIO -cdef class CapturingWrapperBase: - cdef unique_ptr[CCapturingAdapterBase] capturing_adapter +cdef class CapturedFrames: + cdef CCapturedFrames c_captured_frames - cdef CCapturingAdapterBase* c_get_adapter(self): - assert self.capturing_adapter.get() != NULL - return self.capturing_adapter.get() - - def get_result(self): - raise NotImplementedError - - -cdef class AnimatedGifCapturingWrapper(CapturingWrapperBase): - def __init__(self): - self.capturing_adapter = \ - unique_ptr[CCapturingAdapterBase](new CMemoryVectorCapturingAdapter()) + cdef void c_attach_captured_frames(self, CCapturedFrames& c_captured_frames): + self.c_captured_frames = c_captured_frames @property - def frames_memoryviews(self): - cdef CMemoryVectorCapturingAdapter* c_capturing_adapter = \ - self.capturing_adapter.get() + def memoryviews(self): cdef cvarray image_array cdef uint8_t* frame_data - cdef tuple frame_shape = self.frame_shape + cdef tuple dimensions = self.dimensions cdef list collected_memoryviews = [] - for frame_data in c_capturing_adapter.frames_uint8(): + for frame_data in self.c_captured_frames.raw_ptr_frames_uint8(): image_array = cvarray( - shape=frame_shape, + shape=dimensions, itemsize=4, format='I', mode='c', allocate_buffer=False, ) image_array.data = frame_data @@ -42,39 +30,37 @@ cdef class AnimatedGifCapturingWrapper(CapturingWrapperBase): return collected_memoryviews @property - def frame_shape(self): - cdef CMemoryVectorCapturingAdapter* c_capturing_adapter = \ - self.capturing_adapter.get() - return (c_capturing_adapter.width(), c_capturing_adapter.height()) + def dimensions(self): + return (self.c_captured_frames.width, self.c_captured_frames.height) + - def get_result(self): - from PIL import Image +class HTMLBase64Image: + def __init__(self, bytes content, str image_type): + self.content_encoded = base64.b64encode(content).decode('ascii') + self.image_type = image_type - cdef object bytes_buffer = BytesIO() - cdef list images = [Image.frombuffer('RGBA', memview.shape, memview) - for memview in self.frames_memoryviews] + def _repr_html_(self): + return ''.format( + self.image_type, self.content_encoded, + ) - images[0].save(bytes_buffer, - format='gif', - save_all=True, - append_images=images[1:], - duration=50, - loop=0) - return AnimatedGifCaptureResult(bytes_buffer) +def generate_gif(CapturedFrames captured_frames, *, duration=33): + from PIL import Image -cdef class AnimatedGifCaptureResult: - cdef object bytes_buffer + cdef object bytes_buffer = BytesIO() + cdef list images = [Image.frombuffer('RGBA', memview.shape, memview) + for memview in captured_frames.memoryviews] - def __init__(self, object bytes_buffer not None): - self.bytes_buffer = bytes_buffer + images[0].save( + bytes_buffer, format='gif', save_all=True, + append_images=images[1:], duration=duration, loop=0, + optimize=False, + ) - def _repr_html_(self): - self.bytes_buffer.seek(0) - return ''.format( - base64.b64encode(self.bytes_buffer.read()).decode('ascii') - ) + bytes_buffer.seek(0) + return HTMLBase64Image(bytes_buffer.read(), 'image/gif') -cdef CapturingWrapperBase c_get_default_capturing_wrapper(): - return AnimatedGifCapturingWrapper() +def generate_auto(CapturedFrames captured_frames): + return generate_gif(captured_frames) diff --git a/src/kaa/engine.pxi b/src/kaa/engine.pxi index c195ac0f..ac7b83a6 100644 --- a/src/kaa/engine.pxi +++ b/src/kaa/engine.pxi @@ -13,14 +13,14 @@ from .kaacore.engine cimport ( CVirtualResolutionMode ) from .kaacore.display cimport CDisplay -from .kaacore.capture cimport CCapturingAdapterBase +from .kaacore.capture cimport CCapturedFrames from .kaacore.log cimport c_emit_log_dynamic, CLogLevel, _log_category_wrapper from .kaacore.clock cimport CDuration from . import __version__ -cdef double DEFAULT_FIXED_FRAMETIME = 1. / 60. +cdef double DEFAULT_FIXED_FRAMETIME = 1. / 30. def _clean_up(): @@ -103,26 +103,27 @@ cdef class _Engine: CScene* c_scene = scene._c_scene.get() CEngine* c_engine = get_c_engine() with nogil: - c_engine.run(c_scene, 0, CDuration(0.), NULL) + c_engine.run(c_scene) def run_capture( self, Scene scene not None, uint32_t frames_limit, - *, double fixed_frametime = DEFAULT_FIXED_FRAMETIME, - CapturingWrapperBase capturing_wrapper = None, + *, double fixed_dt=DEFAULT_FIXED_FRAMETIME, + frames_preview_generator=None, ): cdef: CScene* c_scene = scene._c_scene.get() CEngine* c_engine = get_c_engine() - CapturingWrapperBase final_capturing_wrapper = ( - capturing_wrapper if capturing_wrapper is not None - else c_get_default_capturing_wrapper() - ) - CCapturingAdapterBase* c_capture_adapter = final_capturing_wrapper.c_get_adapter() - CDuration c_duration = CDuration(fixed_frametime) + CDuration c_duration = CDuration(fixed_dt) + CCapturedFrames c_captured_frames + CapturedFrames captured_frames = CapturedFrames.__new__(CapturedFrames) with nogil: - c_engine.run(c_scene, frames_limit, c_duration, c_capture_adapter) + c_captured_frames = c_engine.run_capture(c_scene, frames_limit, c_duration) + + if frames_preview_generator is None: + frames_preview_generator = generate_auto - return final_capturing_wrapper.get_result() + captured_frames.c_attach_captured_frames(c_captured_frames) + return frames_preview_generator(captured_frames) def quit(self): get_c_engine().quit() diff --git a/src/kaa/kaacore/capture.pxd b/src/kaa/kaacore/capture.pxd index 5be12ce4..3a9afce9 100644 --- a/src/kaa/kaacore/capture.pxd +++ b/src/kaa/kaacore/capture.pxd @@ -1,4 +1,4 @@ -from libcpp.memory cimport unique_ptr +from libcpp.memory cimport shared_ptr from libcpp.vector cimport vector from libc.stdint cimport uint8_t, uint32_t @@ -6,10 +6,8 @@ from .exceptions cimport raise_py_error cdef extern from "kaacore/capture.h" namespace "kaacore" nogil: - cdef cppclass CCapturingAdapterBase "kaacore::CapturingAdapterBase": - uint32_t width() except +raise_py_error - uint32_t height() except +raise_py_error + cdef cppclass CCapturedFrames "kaacore::CapturedFrames": + uint32_t width + uint32_t height - cdef cppclass CMemoryVectorCapturingAdapter "kaacore::MemoryVectorCapturingAdapter"(CCapturingAdapterBase): - size_t frames_count() except +raise_py_error - vector[uint8_t*] frames_uint8() except +raise_py_error + vector[uint8_t*] raw_ptr_frames_uint8() except +raise_py_error diff --git a/src/kaa/kaacore/engine.pxd b/src/kaa/kaacore/engine.pxd index 11b5ccf4..be3c9b31 100644 --- a/src/kaa/kaacore/engine.pxd +++ b/src/kaa/kaacore/engine.pxd @@ -1,10 +1,10 @@ from libcpp.string cimport string from libcpp.memory cimport unique_ptr from libcpp.vector cimport vector -from libc.stdint cimport int32_t, uint64_t +from libc.stdint cimport uint32_t, uint64_t from .clock cimport CDuration -from .capture cimport CCapturingAdapterBase +from .capture cimport CCapturedFrames from .display cimport CDisplay from .scenes cimport CScene from .window cimport CWindow @@ -46,10 +46,10 @@ cdef extern from "kaacore/engine.h" namespace "kaacore" nogil: vector[CDisplay] get_displays() except +raise_py_error CDuration total_time() except +raise_py_error double get_fps() except +raise_py_error - CDuration total_time() except +raise_py_error - vector[CDisplay] get_displays() except +raise_py_error - void run(CScene* c_scene, uint32_t frames_limit, CDuration frame_fixed_duration, - CCapturingAdapterBase* capturing_adapter) except +raise_py_error + + void run(CScene* c_scene) except +raise_py_error + CCapturedFrames run_capture(CScene* c_scene, uint32_t frames_limit, + CDuration fixed_dt) except +raise_py_error void change_scene(CScene* c_scene) except +raise_py_error void quit() except +raise_py_error